diff --git a/core/modules/ckeditor/tests/src/Traits/CKEditorTestTrait.php b/core/modules/ckeditor/tests/src/Traits/CKEditorTestTrait.php new file mode 100644 index 0000000000..24db1b14ae --- /dev/null +++ b/core/modules/ckeditor/tests/src/Traits/CKEditorTestTrait.php @@ -0,0 +1,99 @@ +getSession()->wait($timeout, $condition); + } + + /** + * Assigns a name to the CKEditor iframe. + * + * @see \Behat\Mink\Session::switchToIFrame() + */ + protected function assignNameToCkeditorIframe() { + $javascript = <<getSession()->evaluateScript($javascript); + } + + /** + * Clicks a CKEditor button. + * + * @param string $name + * The name of the button, such as `drupallink`, `source`, etc. + */ + protected function pressEditorButton($name) { + $this->getEditorButton($name)->click(); + } + + /** + * Waits for a CKEditor button and returns it when available and visible. + * + * @param string $name + * The name of the button, such as `drupallink`, `source`, etc. + * + * @return \Behat\Mink\Element\NodeElement|null + * The page element node if found, NULL if not. + */ + protected function getEditorButton($name) { + $this->getSession()->switchToIFrame(); + $button = $this->assertSession()->waitForElementVisible('css', 'a.cke_button__' . $name); + $this->assertNotEmpty($button); + + return $button; + } + + /** + * Asserts a CKEditor button is disabled. + * + * @param string $name + * The name of the button, such as `drupallink`, `source`, etc. + */ + protected function assertEditorButtonDisabled($name) { + $button = $this->getEditorButton($name); + $this->assertTrue($button->hasClass('cke_button_disabled')); + $this->assertSame('true', $button->getAttribute('aria-disabled')); + } + + /** + * Asserts a CKEditor button is enabled. + * + * @param string $name + * The name of the button, such as `drupallink`, `source`, etc. + */ + protected function assertEditorButtonEnabled($name) { + $button = $this->getEditorButton($name); + $this->assertFalse($button->hasClass('cke_button_disabled')); + $this->assertSame('false', $button->getAttribute('aria-disabled')); + } + +} diff --git a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php index e311a14993..d0e7588697 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -9,6 +9,7 @@ use Drupal\filter\Entity\FilterFormat; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\media\Entity\Media; +use Drupal\Tests\ckeditor\Traits\CKEditorTestTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait; use Drupal\Tests\TestFileCreationTrait; @@ -18,6 +19,7 @@ */ class CKEditorIntegrationTest extends WebDriverTestBase { + use CKEditorTestTrait; use MediaTypeCreationTrait; use TestFileCreationTrait; @@ -668,20 +670,6 @@ protected function setCaption($text) { $this->getSession()->executeScript($select_and_edit_caption); } - /** - * Assigns a name to the CKEditor iframe. - * - * @see \Behat\Mink\Session::switchToIFrame() - */ - protected function assignNameToCkeditorIframe() { - $javascript = <<getSession()->evaluateScript($javascript); - } - /** * Assigns a name to the CKEditor context menu iframe. * @@ -698,82 +686,6 @@ protected function assignNameToCkeditorPanelIframe() { $this->getSession()->evaluateScript($javascript); } - /** - * Clicks a CKEditor button. - * - * @param string $name - * The name of the button, such as drupalink, source, etc. - */ - protected function pressEditorButton($name) { - $this->getSession()->switchToIFrame(); - $button = $this->assertSession()->waitForElementVisible('css', 'a.cke_button__' . $name); - $this->assertNotEmpty($button); - $button->click(); - } - - /** - * Waits for a CKEditor button and returns it when available and visible. - * - * @param string $name - * The name of the button, such as drupalink, source, etc. - * - * @return \Behat\Mink\Element\NodeElement|null - * The page element node if found, NULL if not. - */ - protected function getEditorButton($name) { - $this->getSession()->switchToIFrame(); - $button = $this->assertSession()->waitForElementVisible('css', 'a.cke_button__' . $name); - $this->assertNotEmpty($button); - - return $button; - } - - /** - * Asserts a CKEditor button is disabled. - * - * @param string $name - * The name of the button, such as `drupallink`, `source`, etc. - */ - protected function assertEditorButtonDisabled($name) { - $button = $this->getEditorButton($name); - $this->assertTrue($button->hasClass('cke_button_disabled')); - $this->assertSame('true', $button->getAttribute('aria-disabled')); - } - - /** - * Asserts a CKEditor button is enabled. - * - * @param string $name - * The name of the button, such as `drupallink`, `source`, etc. - */ - protected function assertEditorButtonEnabled($name) { - $button = $this->getEditorButton($name); - $this->assertFalse($button->hasClass('cke_button_disabled')); - $this->assertSame('false', $button->getAttribute('aria-disabled')); - } - - /** - * Waits for CKEditor to initialize. - * - * @param string $instance_id - * The CKEditor instance ID. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - */ - protected function waitForEditor($instance_id = 'edit-body-0-value', $timeout = 10000) { - $condition = <<getSession()->wait($timeout, $condition); - } - /** * Opens the context menu for the currently selected widget. * diff --git a/core/modules/media_library/css/media_library.module.css b/core/modules/media_library/css/media_library.module.css index 3e2cdde7d1..e0d790b0cf 100644 --- a/core/modules/media_library/css/media_library.module.css +++ b/core/modules/media_library/css/media_library.module.css @@ -2,6 +2,14 @@ * @file media_library.module.css */ +/** + * By default, the dialog is too narrow to be usable. + * @see Drupal.ckeditor.openDialog() + */ +.ui-dialog--narrow.media-library-widget-modal { + max-width: 75%; +} + .media-library-wrapper { display: flex; } diff --git a/core/modules/media_library/js/plugins/drupalmedialibrary/icons/drupalmedialibrary.png b/core/modules/media_library/js/plugins/drupalmedialibrary/icons/drupalmedialibrary.png new file mode 100644 index 0000000000..8d80f2cf54 --- /dev/null +++ b/core/modules/media_library/js/plugins/drupalmedialibrary/icons/drupalmedialibrary.png @@ -0,0 +1,3 @@ +PNG + + IHDR7IDATx}A H@B% !.?=M:uIJ+-FG> > Wz?"%[`"DntKz|b(Q6( QgP(ּg0y[d# |YF0 H-/Dytz-I |}Ƃry)IENDB` \ No newline at end of file diff --git a/core/modules/media_library/js/plugins/drupalmedialibrary/icons/hidpi/drupalmedialibrary.png b/core/modules/media_library/js/plugins/drupalmedialibrary/icons/hidpi/drupalmedialibrary.png new file mode 100644 index 0000000000..7692dd607c --- /dev/null +++ b/core/modules/media_library/js/plugins/drupalmedialibrary/icons/hidpi/drupalmedialibrary.png @@ -0,0 +1,5 @@ +PNG + + IHDR DQPLTEGGGHHHHHHHHHHHHHHHHHHHHHHHHEEEHHHIII@@@IIIHHHGGGIIIGGGIIIIIIIIIHHHGGGGGGFFFGGGntRNSp߿@`0p Po_ϏPZIDATxv EQQ> =BcMs7)=ui]=C>{>d{cIS08i @ t&wL=5"VEɨSs¹&;A`um+@ IsBjb0zpu#0A& +#qNck6捩Ug^ +bSY3IENDB` \ No newline at end of file diff --git a/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.es6.js b/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.es6.js new file mode 100644 index 0000000000..4e22caab7e --- /dev/null +++ b/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.es6.js @@ -0,0 +1,51 @@ +/** + * @file + * Drupal Media Library plugin. + */ + +(function(Drupal, CKEDITOR) { + CKEDITOR.plugins.add('drupalmedialibrary', { + requires: 'drupalmedia', + icons: 'drupalmedialibrary', + hidpi: true, + beforeInit(editor) { + editor.addCommand('drupalmedialibrary', { + allowedContent: + 'drupal-media[!data-entity-type,!data-entity-uuid,data-align,data-caption,alt,title]', + requiredContent: 'drupal-media[data-entity-type,data-entity-uuid]', + modes: { wysiwyg: 1 }, + // There is an edge case related to the undo functionality that will + // be resolved in https://www.drupal.org/project/drupal/issues/3073294. + canUndo: true, + exec(editor) { + const saveCallback = function(values) { + editor.fire('saveSnapshot'); + const mediaElement = editor.document.createElement('drupal-media'); + const attributes = values.attributes; + Object.keys(attributes).forEach(key => { + mediaElement.setAttribute(key, attributes[key]); + }); + editor.insertHtml(mediaElement.getOuterHtml()); + editor.fire('saveSnapshot'); + }; + + // @see \Drupal\media_library\MediaLibraryUiBuilder::dialogOptions() + Drupal.ckeditor.openDialog( + editor, + editor.config.DrupalMediaLibrary_url, + {}, + saveCallback, + editor.config.DrupalMediaLibrary_dialogOptions, + ); + }, + }); + + if (editor.ui.addButton) { + editor.ui.addButton('DrupalMediaLibrary', { + label: Drupal.t('Insert from Media Library'), + command: 'drupalmedialibrary', + }); + } + }, + }); +})(Drupal, CKEDITOR); diff --git a/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.js b/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.js new file mode 100644 index 0000000000..2a3108022c --- /dev/null +++ b/core/modules/media_library/js/plugins/drupalmedialibrary/plugin.js @@ -0,0 +1,47 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (Drupal, CKEDITOR) { + + "use strict"; + + CKEDITOR.plugins.add('drupalmedialibrary', { + requires: 'drupalmedia', + icons: 'drupalmedialibrary', + hidpi: true, + beforeInit: function beforeInit(editor) { + editor.addCommand('drupalmedialibrary', { + allowedContent: 'drupal-media[!data-entity-type,!data-entity-uuid,data-align,data-caption,alt,title]', + requiredContent: 'drupal-media[data-entity-type,data-entity-uuid]', + modes: { wysiwyg: 1 }, + + canUndo: true, + exec: function exec(editor) { + var saveCallback = function saveCallback(values) { + editor.fire('saveSnapshot'); + var mediaElement = editor.document.createElement('drupal-media'); + var attributes = values.attributes; + for (var key in attributes) { + mediaElement.setAttribute(key, attributes[key]); + } + editor.insertHtml(mediaElement.getOuterHtml()); + editor.fire('saveSnapshot'); + }; + + Drupal.ckeditor.openDialog(editor, editor.config.DrupalMediaLibrary_url, {}, saveCallback, editor.config.DrupalMediaLibrary_dialogOptions); + } + }); + + if (editor.ui.addButton) { + editor.ui.addButton('DrupalMediaLibrary', { + label: Drupal.t('Insert from Media Library'), + command: 'drupalmedialibrary' + }); + } + } + }); +})(Drupal, CKEDITOR); \ No newline at end of file diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module index f0c8186d9d..860035e72b 100644 --- a/core/modules/media_library/media_library.module +++ b/core/modules/media_library/media_library.module @@ -26,6 +26,8 @@ use Drupal\views\Form\ViewsForm; use Drupal\views\Plugin\views\cache\CachePluginBase; use Drupal\views\ViewExecutable; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Component\Serialization\Json; /** * Implements hook_help(). @@ -342,3 +344,73 @@ function _media_library_configure_view_display(MediaTypeInterface $type) { ]); return (bool) $display->save(); } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_library_form_filter_format_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so so we can ensure the media_embed + // filter is enabled when the DrupalMediaLibrary button is enabled. + $form['#validate'][] = 'media_library_filter_format_edit_form_validate'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_library_form_filter_format_add_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so so we can ensure the media_embed + // filter is enabled when the DrupalMediaLibrary button is enabled. + $form['#validate'][] = 'media_library_filter_format_edit_form_validate'; +} + +/** + * Validate callback to ensure the DrupalMediaLibrary button can work correctly. + */ +function media_library_filter_format_edit_form_validate($form, FormStateInterface $form_state) { + if ($form_state->getTriggeringElement()['#name'] !== 'op') { + return; + } + + // The "DrupalMediaLibrary" button is for the CKEditor text editor. + if ($form_state->getValue(['editor', 'editor']) !== 'ckeditor') { + return; + } + + $button_group_path = [ + 'editor', + 'settings', + 'toolbar', + 'button_groups', + ]; + + if ($button_groups = $form_state->getValue($button_group_path)) { + $buttons = []; + $button_groups = Json::decode($button_groups); + + foreach ($button_groups as $button_row) { + foreach ($button_row as $button_group) { + $buttons = array_merge($buttons, array_values($button_group['items'])); + } + } + + $get_filter_label = function ($filter_plugin_id) use ($form) { + return (string) $form['filters']['order'][$filter_plugin_id]['filter']['#markup']; + }; + + if (in_array('DrupalMediaLibrary', $buttons, TRUE)) { + $media_embed_enabled = $form_state->getValue([ + 'filters', + 'media_embed', + 'status', + ]); + + if (!$media_embed_enabled) { + $error_message = new TranslatableMarkup('The %media-embed-filter-label filter must be enabled to use the %drupal-media-library-button button.', [ + '%media-embed-filter-label' => $get_filter_label('media_embed'), + '%drupal-media-library-button' => new TranslatableMarkup('Insert from Media Library'), + ]); + $form_state->setErrorByName('filters', $error_message); + } + } + } +} diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml index b2d06643b5..eaad55fcf5 100644 --- a/core/modules/media_library/media_library.services.yml +++ b/core/modules/media_library/media_library.services.yml @@ -13,3 +13,6 @@ services: media_library.opener.field_widget: class: Drupal\media_library\MediaLibraryFieldWidgetOpener arguments: ['@entity_type.manager'] + media_library.opener.editor: + class: Drupal\media_library\MediaLibraryEditorOpener + arguments: ['@entity_type.manager'] diff --git a/core/modules/media_library/src/MediaLibraryEditorOpener.php b/core/modules/media_library/src/MediaLibraryEditorOpener.php new file mode 100644 index 0000000000..a31290fca6 --- /dev/null +++ b/core/modules/media_library/src/MediaLibraryEditorOpener.php @@ -0,0 +1,83 @@ +filterStorage = $entity_type_manager->getStorage('filter_format'); + $this->mediaStorage = $entity_type_manager->getStorage('media'); + } + + /** + * {@inheritdoc} + */ + public function checkAccess(MediaLibraryState $state, AccountInterface $account) { + $filter_format_id = $state->getOpenerParameters()['filter_format_id']; + $filter_format = $this->filterStorage->load($filter_format_id); + if (empty($filter_format)) { + return AccessResult::forbidden() + ->addCacheTags(['filter_format_list']) + ->setReason("The text format '$filter_format_id' could not be loaded."); + } + $filters = $filter_format->filters(); + return $filter_format->access('use', $account, TRUE) + ->andIf(AccessResult::allowedIf($filters->has('media_embed') && $filters->get('media_embed')->status === TRUE)); + } + + /** + * {@inheritdoc} + */ + public function getSelectionResponse(MediaLibraryState $state, array $selected_ids) { + $selected_media = $this->mediaStorage->load(reset($selected_ids)); + + $response = new AjaxResponse(); + $values = [ + 'attributes' => [ + 'data-entity-type' => 'media', + 'data-entity-uuid' => $selected_media->uuid(), + 'data-align' => 'center', + ], + ]; + $response->addCommand(new EditorDialogSave($values)); + + return $response; + } + +} diff --git a/core/modules/media_library/src/Plugin/CKEditorPlugin/DrupalMediaLibrary.php b/core/modules/media_library/src/Plugin/CKEditorPlugin/DrupalMediaLibrary.php new file mode 100644 index 0000000000..74d81af95c --- /dev/null +++ b/core/modules/media_library/src/Plugin/CKEditorPlugin/DrupalMediaLibrary.php @@ -0,0 +1,153 @@ +moduleExtensionList = $extension_list_module; + $this->mediaTypeStorage = $entity_type_manager->getStorage('media_type'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('extension.list.module'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function isInternal() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(Editor $editor) { + return [ + 'drupalmedia', + ]; + } + + /** + * {@inheritdoc} + */ + public function getLibraries(Editor $editor) { + return [ + 'editor/drupal.editor.dialog', + ]; + } + + /** + * {@inheritdoc} + */ + public function getFile() { + return $this->moduleExtensionList->getPath('media_library') . '/js/plugins/drupalmedialibrary/plugin.js'; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + $media_type_ids = $this->mediaTypeStorage->getQuery()->execute(); + + if (in_array('image', $media_type_ids, TRUE)) { + // Due to a bug where the active item styling and the focus styling + // create the visual appearance of two active items, we'll move + // the 'image' media type to first position, so that the focused item and + // the active item are the same. + // This workaround can be removed once this issue is fixed: + // @see https://www.drupal.org/project/drupal/issues/3073799 + array_unshift($media_type_ids, 'image'); + $media_type_ids = array_unique($media_type_ids); + } + + $state = MediaLibraryState::create( + 'media_library.opener.editor', + $media_type_ids, + reset($media_type_ids), + -1, + ['filter_format_id' => $editor->getFilterFormat()->id()] + ); + + return [ + 'DrupalMediaLibrary_url' => Url::fromRoute('media_library.ui') + ->setOption('query', $state->all()) + ->toString(TRUE) + ->getGeneratedUrl(), + 'DrupalMediaLibrary_dialogOptions' => MediaLibraryUiBuilder::dialogOptions(), + ]; + } + + /** + * {@inheritdoc} + */ + public function getButtons() { + return [ + 'DrupalMediaLibrary' => [ + 'label' => $this->t('Insert from Media Library'), + 'image' => $this->moduleExtensionList->getPath('media_library') . '/js/plugins/drupalmedialibrary/icons/drupalmedialibrary.png', + ], + ]; + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php new file mode 100644 index 0000000000..d6d3270dba --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -0,0 +1,206 @@ + 'test_format', + 'name' => 'Test format', + 'filters' => [ + 'media_embed' => ['status' => TRUE], + ], + ])->save(); + Editor::create([ + 'editor' => 'ckeditor', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + [ + [ + 'name' => 'Main', + 'items' => [ + 'Source', + 'Undo', + 'Redo', + ], + ], + ], + [ + [ + 'name' => 'Embeds', + 'items' => [ + 'DrupalMediaLibrary', + ], + ], + ], + ], + ], + ], + ])->save(); + + $this->drupalCreateContentType(['type' => 'blog']); + + // Note that media_install() grants 'view media' to all users by default. + $this->user = $this->drupalCreateUser([ + 'use text format test_format', + 'access media overview', + 'create blog content', + ]); + + // Create a media type that starts with the letter a, to test tab order. + $this->createMediaType('image', ['id' => 'Arrakis', 'label' => 'Arrakis']); + + // Create a sample media entity to be embedded. + $this->createMediaType('image', ['id' => 'image', 'label' => 'Image']); + File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ])->save(); + $this->media = Media::create([ + 'bundle' => 'image', + 'name' => 'Fear is the mind-killer', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'default alt', + 'title' => 'default title', + ], + ], + ]); + $this->media->save(); + + $this->drupalLogin($this->user); + } + + /** + * Tests that media_embed filter is required to enable the DrupalMediaLibrary + * button. + */ + public function testConfigurationValidation() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $admin_user = $this->drupalCreateUser([ + 'access administration pages', + 'administer site configuration', + 'administer filters', + ]); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/config/content/formats/manage/test_format'); + $page->uncheckField('filters[media_embed][status]'); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The Embed media filter must be enabled to use the Insert from Media Library button.'); + $page->checkField('filters[media_embed][status]'); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The text format Test format has been updated.'); + } + + /** + * Tests using DrupalMediaLibrary button to embed media into CKEditor. + */ + public function testButton() { + $this->drupalGet('/node/add/blog'); + $this->waitForEditor(); + $this->pressEditorButton('drupalmedialibrary'); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + $this->assertNotEmpty($assert_session->waitForId('drupal-modal')); + + // Test that the order is the order set in DrupalMediaLibrary::getConfig(). + $tabs = $page->findAll('css', '.media-library-menu__link'); + $expected_tab_order = [ + 'Show Image media (selected)', + 'Show Arrakis media', + ]; + foreach ($tabs as $key => $tab) { + $this->assertSame($expected_tab_order[$key], $tab->getText()); + } + + $assert_session->elementExists('css', '.media-library-item')->click(); + $assert_session->elementExists('css', 'button.media-library-select.button.button--primary')->click(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 2000)); + // @todo Inserting media embed should enable undo. + // @see https://www.drupal.org/project/drupal/issues/3073294 + $this->pressEditorButton('source'); + $value = $assert_session->elementExists('css', 'textarea.cke_source')->getValue(); + $dom = Html::load($value); + $xpath = new \DOMXPath($dom); + $drupal_media = $xpath->query('//drupal-media')[0]; + $expected_attributes = [ + 'data-entity-type' => 'media', + 'data-entity-uuid' => $this->media->uuid(), + 'data-align' => 'center', + ]; + foreach ($expected_attributes as $name => $expected) { + $this->assertSame($expected, $drupal_media->getAttribute($name)); + } + $this->pressEditorButton('source'); + // Why do we keep switching to the 'ckeditor' iframe? Because the buttons + // are in a separate iframe from the markup, so after calling + // ::pressEditorButton() (which switches to the button iframe), we'll need + // to switch back to the CKEditor iframe. + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonEnabled('undo'); + $this->pressEditorButton('undo'); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonDisabled('undo'); + $this->pressEditorButton('redo'); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonEnabled('undo'); + } + +} diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php index 75d064a5e9..389561d4e2 100644 --- a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php +++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php @@ -31,6 +31,7 @@ class MediaLibraryAccessTest extends KernelTestBase { 'media', 'media_library', 'media_library_test', + 'filter', 'file', 'field', 'image', @@ -50,6 +51,7 @@ protected function setUp() { $this->installSchema('file', 'file_usage'); $this->installSchema('system', ['sequences', 'key_value_expire']); $this->installEntitySchema('entity_test'); + $this->installEntitySchema('filter_format'); $this->installEntitySchema('media'); $this->installConfig([ 'field', @@ -124,6 +126,76 @@ public function testFieldWidgetEntityCreateAccess() { $this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']); } + /** + * @covers \Drupal\media_library\MediaLibraryEditorOpener::checkAccess + * + * @param bool $media_embed_enabled + * Whether to test with media_embed filter enabled on the text format. + * @param bool $can_use_format + * Whether the logged in user is allowed to use the text format. + * + * @dataProvider editorOpenerAccessProvider + */ + public function testEditorOpenerAccess($media_embed_enabled, $can_use_format) { + $format = $this->container + ->get('entity_type.manager') + ->getStorage('filter_format')->create([ + 'format' => $this->randomMachineName(), + 'name' => $this->randomString(), + 'filters' => [ + 'media_embed' => ['status' => $media_embed_enabled], + ], + ]); + $format->save(); + + $permissions = [ + 'access media overview', + 'view media', + ]; + if ($can_use_format) { + $permissions[] = $format->getPermissionName(); + } + + $state = MediaLibraryState::create( + 'media_library.opener.editor', + ['image'], + 'image', + 1, + ['filter_format_id' => $format->id()] + ); + + $access_result = $this->container + ->get('media_library.ui_builder') + ->checkAccess($this->createUser($permissions), $state); + + if ($media_embed_enabled && $can_use_format) { + $this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['user.permissions']); + } + else { + $this->assertAccess($access_result, FALSE, NULL, [], ['user.permissions']); + } + } + + /** + * Data provider for ::testEditorOpenerAccess. + */ + public function editorOpenerAccessProvider() { + return [ + 'media_embed filter enabled' => [ + TRUE, + TRUE, + ], + 'media_embed filter disabled' => [ + FALSE, + TRUE, + ], + 'media_embed filter enabled, user not allowed to use text format' => [ + TRUE, + FALSE, + ], + ]; + } + /** * Tests that the field widget opener respects entity-specific access. */