Unverified Commit ab035217 authored by lauriii's avatar lauriii
Browse files

Issue #3269657 by hooroomoo, Wim Leers: [drupalMedia] The CKEditor 4 → 5...

Issue #3269657 by hooroomoo, Wim Leers: [drupalMedia] The CKEditor 4 → 5 upgrade path for the media_embed filter should not forcefully allow the `data-view-mode` attribute on `<drupal-media>`

(cherry picked from commit 32bf0369)
parent 004fa86e
......@@ -644,7 +644,7 @@ media_media:
library: ckeditor5/drupal.ckeditor5.media
class: Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
elements:
- <drupal-media data-entity-type data-entity-uuid data-view-mode alt>
- <drupal-media data-entity-type data-entity-uuid alt data-view-mode>
conditions:
filter: media_embed
......
......@@ -4,6 +4,7 @@ ckeditor5_valid_pair__format_and_editor:
label: 'Text Format plus Text Editor pair using CKEditor 5'
constraints:
CKEditor5FundamentalCompatibility: []
CKEditor5MediaAndFilterSettingsInSync: []
mapping:
settings:
type: editor.settings.ckeditor5
......
......@@ -102,3 +102,14 @@ ckeditor5.plugin.ckeditor5_list:
label: 'Allow start index'
constraints:
NotNull: []
# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
ckeditor5.plugin.media_media:
type: mapping
label: List
mapping:
allow_view_mode_override:
type: boolean
label: 'Allow view mode override'
constraints:
NotNull: []
......@@ -60,6 +60,7 @@
* cke5_plugin_elements_subset_configuration = {
* "ckeditor5_heading",
* "ckeditor5_list",
* "media_media",
* }
* )
*
......@@ -240,6 +241,18 @@ public function computeCKEditor5PluginSubsetConfiguration(string $cke5_plugin_id
$configuration['startIndex'] = !empty($restrictions['allowed']['ol']['start']);
return $configuration;
case 'media_media':
$restrictions = $text_format->getHtmlRestrictions();
if ($restrictions === FALSE) {
// The default is to not allow the user to override the default view mode.
// @see \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media::defaultConfiguration()
return NULL;
}
$configuration = [];
// Check if data-view-mode is allowed.
$configuration['allow_view_mode_override'] = !empty($restrictions['allowed']['drupal-media']['data-view-mode']);
return $configuration;
default:
throw new \OutOfBoundsException();
}
......
......@@ -4,7 +4,11 @@
namespace Drupal\ckeditor5\Plugin\CKEditor5Plugin;
use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
use Drupal\ckeditor5\Plugin\CKEditor5PluginElementsSubsetInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\editor\EditorInterface;
......@@ -12,6 +16,7 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\ckeditor5\HTMLRestrictions;
/**
* CKEditor 5 Media plugin.
......@@ -21,9 +26,10 @@
* @internal
* Plugin classes are internal.
*/
class Media extends CKEditor5PluginDefault implements ContainerFactoryPluginInterface {
class Media extends CKEditor5PluginDefault implements ContainerFactoryPluginInterface, CKEditor5PluginConfigurableInterface, CKEditor5PluginElementsSubsetInterface {
use DynamicPluginConfigWithCsrfTokenUrlTrait;
use CKEditor5PluginConfigurableTrait;
/**
* The entity display repository.
......@@ -173,7 +179,9 @@ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterf
] = self::configureViewModes($editor);
$dynamic_plugin_config['drupalElementStyles']['viewMode'] = $element_style_configuration;
$dynamic_plugin_config['drupalMedia']['toolbar'][] = $toolbar_configuration;
if ($this->getConfiguration()['allow_view_mode_override']) {
$dynamic_plugin_config['drupalMedia']['toolbar'][] = $toolbar_configuration;
}
$dynamic_plugin_config['drupalMedia']['metadataUrl'] = self::getUrlWithReplacedCsrfTokenPlaceholder(
Url::fromRoute('ckeditor5.media_entity_metadata')
->setRouteParameter('editor', $editor->id())
......@@ -182,4 +190,51 @@ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterf
return $dynamic_plugin_config;
}
/**
* {@inheritdoc}
*/
public function getElementsSubset(): array {
$all_elements = $this->getPluginDefinition()->getElements();
$subset = HTMLRestrictions::fromString(implode($all_elements));
$view_mode_override_enabled = $this->getConfiguration()['allow_view_mode_override'];
if (!$view_mode_override_enabled) {
$subset = $subset->diff(HTMLRestrictions::fromString('<drupal-media data-view-mode>'));
}
return $subset->toCKEditor5ElementsArray();
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return ['allow_view_mode_override' => FALSE];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['allow_view_mode_override'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow the user to override the default view mode'),
'#default_value' => $this->configuration['allow_view_mode_override'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$form_value = $form_state->getValue('allow_view_mode_override');
$form_state->setValue('allow_view_mode_override', (bool) $form_value);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['allow_view_mode_override'] = $form_state->getValue('allow_view_mode_override');
}
}
<?php
declare(strict_types = 1);
namespace Drupal\ckeditor5\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Ensure CKEditor 5 media plugin's and media filter's settings are in sync.
*
* @Constraint(
* id = "CKEditor5MediaAndFilterSettingsInSync",
* label = @Translation("CKEditor 5 Media plugin in sync with filter settings", context = "Validation"),
* )
*
* @internal
*/
class CKEditor5MediaAndFilterSettingsInSyncConstraint extends Constraint {
/**
* The default violation message.
*
* @var string
*/
public $message = 'The CKEditor 5 "%cke5_media_plugin_label" plugin\'s "%cke5_allow_view_mode_override_label" setting should be in sync with the "%filter_media_plugin_label" filter\'s "%filter_media_allowed_view_modes_label" setting: when checked, two or more view modes must be allowed by the filter.';
}
<?php
declare(strict_types = 1);
namespace Drupal\ckeditor5\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Drupal\filter\FilterPluginManager;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* CKEditor 5 Media plugin in sync with the filter settings validator.
*
* @internal
*/
class CKEditor5MediaAndFilterSettingsInSyncConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
use PluginManagerDependentValidatorTrait;
use TextEditorObjectDependentValidatorTrait;
use StringTranslationTrait;
/**
* The filter plugin manager service.
*
* @var \Drupal\filter\FilterPluginManager
*/
protected $filterPluginManager;
/**
* The typed config manager service.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* Constructs a new CKEditor5MediaAndFilterSettingsInSyncConstraintValidator.
*
* @param \Drupal\filter\FilterPluginManager $filter_plugin_manager
* The filter plugin manager service.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed config manager service.
*/
public function __construct(FilterPluginManager $filter_plugin_manager, TypedConfigManagerInterface $typed_config_manager) {
$this->filterPluginManager = $filter_plugin_manager;
$this->typedConfigManager = $typed_config_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.filter'),
$container->get('config.typed'),
);
}
/**
* {@inheritdoc}
*
* @throws \Symfony\Component\Validator\Exception\UnexpectedTypeException
* Thrown when the given constraint is not supported by this validator.
*/
public function validate($toolbar_item, Constraint $constraint) {
if (!$constraint instanceof CKEditor5MediaAndFilterSettingsInSyncConstraint) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\CKEditor5MediaAndFilterSettingsInSync');
}
$text_editor = $this->createTextEditorObjectFromContext();
if (isset($text_editor->getSettings()['plugins']['media_media'])) {
$cke5_plugin_overrides_allowed = $text_editor->getSettings()['plugins']['media_media']['allow_view_mode_override'];
$filter_allowed_view_modes = $text_editor->getFilterFormat()->filters('media_embed')->getConfiguration()['settings']['allowed_view_modes'];
$filter_media_plugin_label = $this->filterPluginManager->getDefinition('media_embed')['title']->render();
$filter_media_allowed_view_modes_label = $this->typedConfigManager->getDefinition('filter_settings.media_embed')['mapping']['allowed_view_modes']['label'];
// Whenever the CKEditor 5 plugin is configured to allow overrides, the
// filter must be configured to allow 2 or more view modes.
if ($cke5_plugin_overrides_allowed && count($filter_allowed_view_modes) < 2) {
$this->context->addViolation($constraint->message, [
'%cke5_media_plugin_label' => $this->t('Media'),
'%cke5_allow_view_mode_override_label' => $this->t('Allow the user to override the default view mode'),
'%filter_media_plugin_label' => $filter_media_plugin_label,
'%filter_media_allowed_view_modes_label' => $filter_media_allowed_view_modes_label,
]);
}
}
}
}
......@@ -7,6 +7,3 @@ ckeditor5_plugin_elements_subset_sneakySuperset:
elements:
- <foo>
- <bar>
# @todo Remove in https://www.drupal.org/project/drupal/issues/3269657
conditions:
filter: media_embed
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\ckeditor5\Functional;
use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\editor\Entity\Editor;
use Drupal\file\Entity\File;
use Drupal\filter\Entity\FilterFormat;
......@@ -78,6 +79,20 @@ protected function setUp(): void {
parent::setUp();
$this->uuidService = $this->container->get('uuid');
EntityViewMode::create([
'id' => 'media.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 1',
])->save();
EntityViewMode::create([
'id' => 'media.view_mode_2',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 2',
])->save();
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
......@@ -94,7 +109,18 @@ protected function setUp(): void {
'filter_html_nofollow' => TRUE,
],
],
'media_embed' => ['status' => TRUE],
'media_embed' => [
'id' => 'media_embed',
'status' => TRUE,
'settings' => [
'default_view_mode' => 'view_mode_1',
'allowed_view_modes' => [
'view_mode_1' => 'view_mode_1',
'view_mode_2' => 'view_mode_2',
],
'allowed_media_types' => [],
],
],
],
'roles' => [RoleInterface::AUTHENTICATED_ID],
]);
......@@ -106,9 +132,26 @@ protected function setUp(): void {
'toolbar' => [
'items' => [],
],
'plugins' => [
'media_media' => [
'allow_view_mode_override' => TRUE,
],
],
],
]);
$this->editor->save();
$filtered_html_format->setFilterConfig('media_embed', [
'status' => TRUE,
'settings' => [
'default_view_mode' => 'view_mode_1',
'allowed_media_types' => [],
'allowed_view_modes' => [
'view_mode_1' => 'view_mode_1',
'view_mode_2' => 'view_mode_2',
],
],
])->save();
$this->assertSame([], array_map(
function (ConstraintViolation $v) {
return (string) $v->getMessage();
......
......@@ -18,8 +18,6 @@ class AdminUiTest extends CKEditor5TestBase {
protected static $modules = [
'media_library',
'ckeditor',
// @todo Remove in https://www.drupal.org/project/drupal/issues/3269657
'ckeditor5_plugin_elements_subset',
'ckeditor5_incompatible_filter_test',
];
......@@ -223,9 +221,7 @@ public function testPluginSettingsFormSection() {
$assert_session->elementExists('css', '[data-drupal-selector="edit-editor-settings-plugins-ckeditor5-sourceediting"]');
// The filter-dependent configurable plugin should not be present.
// @todo Change to `media_media` plugin in https://www.drupal.org/project/drupal/issues/3269657
// cSpell:disable-next-line
$assert_session->elementNotExists('css', '[data-drupal-selector="edit-editor-settings-plugins-ckeditor5-plugin-elements-subset-sneakysuperset"]');
$assert_session->elementNotExists('css', '[data-drupal-selector="edit-editor-settings-plugins-media-media"]');
// Enable the filter that the configurable plugin depends on.
$this->assertTrue($page->hasUncheckedField('filters[media_embed][status]'));
......@@ -233,8 +229,7 @@ public function testPluginSettingsFormSection() {
$assert_session->assertWaitOnAjaxRequest();
// The filter-dependent configurable plugin should be present.
// cSpell:disable-next-line
$assert_session->elementExists('css', '[data-drupal-selector="edit-editor-settings-plugins-ckeditor5-plugin-elements-subset-sneakysuperset"]');
$assert_session->elementExists('css', '[data-drupal-selector="edit-editor-settings-plugins-media-media"]');
}
/**
......
......@@ -2,6 +2,7 @@
namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Symfony\Component\Yaml\Yaml;
......@@ -369,6 +370,20 @@ public function testMediaElementAllowedTags() {
$this->createNewTextFormat($page, $assert_session);
EntityViewMode::create([
'id' => 'media.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 1',
])->save();
EntityViewMode::create([
'id' => 'media.view_mode_2',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 2',
])->save();
// Allowed HTML field is readonly and its wrapper has a form-disabled class.
$this->assertNotEmpty($assert_session->waitForElement('css', '.js-form-item-filters-filter-html-settings-allowed-html.form-disabled'));
$allowed_html_field = $assert_session->fieldExists('filters[filter_html][settings][allowed_html]');
......@@ -383,8 +398,20 @@ public function testMediaElementAllowedTags() {
$assert_session->assertWaitOnAjaxRequest();
$assert_session->responseContains('Media types selectable in the Media Library');
$allowed_with_media = $this->allowedElements . ' <drupal-media data-entity-type data-entity-uuid data-view-mode alt>';
$page->clickLink('Embed media');
$page->checkField('filters[media_embed][settings][allowed_view_modes][view_mode_1]');
$page->checkField('filters[media_embed][settings][allowed_view_modes][view_mode_2]');
$assert_session->assertWaitOnAjaxRequest();
$allowed_with_media = $this->allowedElements . ' <drupal-media data-entity-type data-entity-uuid alt data-view-mode>';
$allowed_with_media_without_view_mode = $this->allowedElements . ' <drupal-media data-entity-type data-entity-uuid alt>';
$assert_session->responseContains('Media types selectable in the Media Library');
$page->clickLink('Media');
$assert_session->waitForText('Allow the user to override the default view mode');
$this->assertTrue($page->hasUncheckedField('editor[settings][plugins][media_media][allow_view_mode_override]'));
$this->assertHtmlEsqueFieldValueEquals('filters[filter_html][settings][allowed_html]', $allowed_with_media_without_view_mode);
$page->checkField('editor[settings][plugins][media_media][allow_view_mode_override]');
$assert_session->assertWaitOnAjaxRequest();
$this->assertHtmlEsqueFieldValueEquals('filters[filter_html][settings][allowed_html]', $allowed_with_media);
$this->saveNewTextFormat($page, $assert_session);
$assert_session->pageTextContains('Added text format ckeditor5.');
......@@ -403,7 +430,7 @@ public function testMediaElementAllowedTags() {
// filter_align is enabled.
$page->checkField('filters[filter_align][status]');
$assert_session->assertWaitOnAjaxRequest();
$this->assertEquals($this->allowedElements . ' <drupal-media data-entity-type data-entity-uuid data-view-mode alt data-align>', $allowed_html_field->getValue());
$this->assertEquals($this->allowedElements . ' <drupal-media data-entity-type data-entity-uuid alt data-view-mode data-align>', $allowed_html_field->getValue());
// Disable media embed.
$this->assertTrue($page->hasCheckedField('filters[media_embed][status]'));
......
......@@ -83,6 +83,9 @@ protected function setUp(): void {
'ckeditor5_sourceEditing' => [
'allowed_tags' => [],
],
'media_media' => [
'allow_view_mode_override' => FALSE,
],
],
],
])->save();
......
......@@ -85,6 +85,20 @@ class MediaTest extends WebDriverTestBase {
protected function setUp(): void {
parent::setUp();
EntityViewMode::create([
'id' => 'media.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 1',
])->save();
EntityViewMode::create([
'id' => 'media.22222',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 2 has Numeric ID',
])->save();
FilterFormat::create([
'format' => 'test_format',
'name' => 'Test format',
......@@ -97,7 +111,17 @@ protected function setUp(): void {
],
'filter_align' => ['status' => TRUE],
'filter_caption' => ['status' => TRUE],
'media_embed' => ['status' => TRUE],
'media_embed' => [
'status' => TRUE,
'settings' => [
'default_view_mode' => 'view_mode_1',
'allowed_view_modes' => [
'view_mode_1' => 'view_mode_1',
'22222' => '22222',
],
'allowed_media_types' => [],
],
],
],
])->save();
Editor::create([
......@@ -116,6 +140,9 @@ protected function setUp(): void {
'ckeditor5_sourceEditing' => [
'allowed_tags' => [],
],
'media_media' => [
'allow_view_mode_override' => TRUE,
],
],
],
'image_upload' => [
......@@ -171,6 +198,22 @@ function (ConstraintViolation $v) {
]);
$this->mediaFile->save();
// Set created media types for each view mode.
EntityViewDisplay::create([
'id' => 'media.image.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'bundle' => 'image',
'mode' => 'view_mode_1',
])->save();
EntityViewDisplay::create([
'id' => 'media.image.22222',
'targetEntityType' => 'media',
'status' => TRUE,
'bundle' => 'image',
'mode' => '22222',
])->save();
// Create a sample host entity to embed media in.
$this->drupalCreateContentType(['type' => 'blog']);
$this->host = $this->createNode([
......@@ -1137,6 +1180,9 @@ public function testDrupalMediaStyleWithClass() {
'ckeditor5_sourceEditing' => [
'allowed_tags' => [],
],
'media_media' => [
'allow_view_mode_override' => TRUE,
],
],
]);
$filter_format = $editor->getFilterFormat();
......@@ -1200,20 +1246,6 @@ function (ConstraintViolation $v) {
* @dataProvider providerTestViewMode
*/
public function testViewMode(bool $with_alignment) {
EntityViewMode::create([
'id' => 'media.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 1',
])->save();
EntityViewMode::create([
'id' => 'media.22222',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => 'View Mode 2 has Numeric ID',
])->save();
EntityViewMode::create([
'id' => 'media.view_mode_3',
'targetEntityType' => 'media',
......@@ -1229,20 +1261,6 @@ public function testViewMode(bool $with_alignment) {
'label' => 'View Mode 4',
])->save();
// Enable view mode 1, 2, 4 for Image.
EntityViewDisplay::create([
'id' => 'media.image.view_mode_1',
'targetEntityType' => 'media',
'status' => TRUE,
'bundle' => 'image',
'mode' => 'view_mode_1',
])->save();
EntityViewDisplay::create([
'id' => 'media.image.22222',
'targetEntityType' => 'media',
'status' => TRUE,
'bundle' => 'image',
'mode' => '22222',
])->save();
EntityViewDisplay::create([