Commit aa2485a2 authored by xjm's avatar xjm

Merged 9.0.6.

parents 9bb004cd b6ca29bf
......@@ -4481,9 +4481,6 @@
"ext-zip": "Enabling the zip extension allows you to unzip archives",
"ext-zlib": "Allow gzip compression of HTTP requests"
},
"bin": [
"bin/composer"
],
"type": "library",
"extra": {
"branch-alias": {
......
......@@ -861,7 +861,8 @@ protected function buildFormAction() {
// https://www.drupal.org/node/2504709.
$parsed = UrlHelper::parse($request_uri);
unset($parsed['query'][static::AJAX_FORM_REQUEST], $parsed['query'][MainContentViewSubscriber::WRAPPER_FORMAT]);
return $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');
$action = $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');
return UrlHelper::filterBadProtocol($action);
}
/**
......
......@@ -559,6 +559,7 @@
}
},
dataType: 'json',
jsonp: false,
type: 'POST',
};
......
......@@ -246,6 +246,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
}
},
dataType: 'json',
jsonp: false,
type: 'POST'
};
......
......@@ -281,6 +281,7 @@
},
ajax: {
dataType: 'json',
jsonp: false,
},
};
......
......@@ -155,7 +155,8 @@
isComposing: false
},
ajax: {
dataType: 'json'
dataType: 'json',
jsonp: false
}
};
Drupal.autocomplete = autocomplete;
......
......@@ -215,6 +215,9 @@
'figcaption',
);
const captionFilter = new CKEDITOR.filter(widgetDefinition.editables.caption.allowedContent);
captionFilter.applyTo(caption);
// Use Drupal's data-placeholder attribute to insert a CSS-based,
// translation-ready placeholder for empty captions. Note that it
// also must to be done for new instances (see
......
......@@ -137,6 +137,8 @@
if (caption) {
var figure = new CKEDITOR.htmlParser.element('figure');
caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
var captionFilter = new CKEDITOR.filter(widgetDefinition.editables.caption.allowedContent);
captionFilter.applyTo(caption);
caption.attributes['data-placeholder'] = placeholderText;
element.replaceWith(figure);
figure.add(element);
......
......@@ -96,6 +96,10 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
foreach ($input['fids'] as $fid) {
if ($file = File::load($fid)) {
$fids[] = $file->id();
if (!$file->access('download')) {
$force_default = TRUE;
break;
}
// Temporary files that belong to other users should never be
// allowed.
if ($file->isTemporary()) {
......
......@@ -92,11 +92,10 @@ public function testPrivateFile() {
$this->drupalGet('node/' . $new_node->id() . '/edit');
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
// Make sure the form submit failed - we stayed on the edit form.
$this->assertUrl('node/' . $new_node->id() . '/edit');
// Check that we got the expected constraint form error.
$constraint = new ReferenceAccessConstraint();
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
$this->assertUrl('node/' . $new_node->id());
// Make sure the submitted hidden file field is empty.
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
$this->assertTrue($new_node->get($field_name)->isEmpty());
// Attempt to reuse the existing file when creating a new node, and confirm
// that access is still denied.
$edit = [];
......@@ -107,9 +106,10 @@ public function testPrivateFile() {
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue(empty($new_node), 'Node was not created.');
$this->assertUrl('node/add/' . $type_name);
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
$this->assertUrl('node/' . $new_node->id());
// Make sure the submitted hidden file field is empty.
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
$this->assertTrue($new_node->get($field_name)->isEmpty());
// Now make file_test_file_download() return everything.
\Drupal::state()->set('file_test.allow_all', TRUE);
......
......@@ -4,9 +4,12 @@
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\filter\FilterPluginManager;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\filter\Render\FilteredMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a filter to caption elements.
......@@ -20,7 +23,43 @@
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
class FilterCaption extends FilterBase {
class FilterCaption extends FilterBase implements ContainerFactoryPluginInterface {
/**
* Filter manager.
*
* @var \Drupal\filter\FilterPluginManager
*/
protected $filterManager;
/**
* Constructs a new FilterCaption.
*
* @param array $configuration
* Configuration.
* @param string $plugin_id
* Plugin ID.
* @param mixed $plugin_definition
* Definition.
* @param \Drupal\filter\FilterPluginManager $filter_manager
* Filter plugin manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, FilterPluginManager $filter_manager = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->filterManager = $filter_manager ?: \Drupal::service('plugin.manager.filter');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.filter')
);
}
/**
* {@inheritdoc}
......@@ -31,6 +70,13 @@ public function process($text, $langcode) {
if (stristr($text, 'data-caption') !== FALSE) {
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
$html_filter = $this->filterManager->createInstance('filter_html', [
'settings' => [
'allowed_html' => '<a href hreflang target rel> <em> <strong> <cite> <code> <br>',
'filter_html_help' => FALSE,
'filter_html_nofollow' => FALSE,
],
]);
foreach ($xpath->query('//*[@data-caption]') as $node) {
// Read the data-caption attribute's value, then delete it.
$caption = Html::escape($node->getAttribute('data-caption'));
......@@ -39,10 +85,19 @@ public function process($text, $langcode) {
// Sanitize caption: decode HTML encoding, limit allowed HTML tags; only
// allow inline tags that are allowed by default, plus <br>.
$caption = Html::decodeEntities($caption);
$caption = FilteredMarkup::create(Xss::filter($caption, ['a', 'em', 'strong', 'cite', 'code', 'br']));
$raw_caption = $caption;
$filtered_caption = $html_filter->process($caption, $langcode);
$result->addCacheableDependency($filtered_caption);
$caption = FilteredMarkup::create($filtered_caption->getProcessedText());
// The caption must be non-empty.
if (mb_strlen($caption) === 0) {
// The caption must be non-empty - however the Media Embed CKEditor
// plugin uses a single space to represent a newly added caption. The
// HTML filter will transform this into an empty string and prevent the
// content editor from adding a new caption. To allow for this we treat
// a raw caption value of ' ' as valid and adding the wrapping figure
// element.
// @see core/modules/media/js/plugins/drupalmedia/plugin.es6.js
if (mb_strlen($caption) === 0 && $raw_caption !== ' ') {
continue;
}
......
......@@ -2,6 +2,7 @@
namespace Drupal\user\Plugin\Block;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
......@@ -155,7 +156,7 @@ public function build() {
public static function renderPlaceholderFormAction() {
return [
'#type' => 'markup',
'#markup' => Url::fromRoute('<current>', [], ['query' => \Drupal::destination()->getAsArray(), 'external' => FALSE])->toString(),
'#markup' => UrlHelper::filterBadProtocol(Url::fromRoute('<current>', [], ['query' => \Drupal::destination()->getAsArray(), 'external' => FALSE])->toString()),
'#cache' => ['contexts' => ['url.path', 'url.query_args']],
];
}
......
......@@ -186,7 +186,11 @@ public function getActiveWorkspace() {
foreach ($this->negotiatorIds as $negotiator_id) {
$negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id);
if ($negotiator->applies($request)) {
if ($active_workspace = $negotiator->getActiveWorkspace($request)) {
// By default, 'view' access is checked when a workspace is activated,
// but it should also be checked when retrieving the currently active
// workspace.
if (($negotiated_workspace = $negotiator->getActiveWorkspace($request)) && $negotiated_workspace->access('view')) {
$active_workspace = $negotiated_workspace;
break;
}
}
......
......@@ -4,6 +4,7 @@
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\UpdatePathTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests that there is no active workspace during database updates.
......@@ -12,12 +13,14 @@
* @group Update
*/
class ActiveWorkspaceUpdateTest extends BrowserTestBase {
use UpdatePathTestTrait;
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['workspaces', 'workspace_update_test'];
protected static $modules = ['workspaces'];
/**
* {@inheritdoc}
......@@ -29,6 +32,11 @@ class ActiveWorkspaceUpdateTest extends BrowserTestBase {
*/
protected function setUp(): void {
parent::setUp();
$this->setUpCurrentUser([], ['view any workspace']);
$this->container->get('module_installer')->install(['workspace_update_test']);
$this->rebuildContainer();
// Ensure the workspace_update_test_post_update_check_active_workspace()
// update runs.
$existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment