Issue #2994699 by Wim Leers, oknate, ckrina, legovaer, phenaproxima, webchick, seanB: Create a CKEditor plugin to select and embed a media item from the Media Library

merge-requests/55/head
webchick 2019-08-12 10:25:03 -07:00
parent e47700f70a
commit 6280478a1a
13 changed files with 793 additions and 90 deletions

View File

@ -0,0 +1,99 @@
<?php
namespace Drupal\Tests\ckeditor\Traits;
/**
* Provides methods to test CKEditor.
*
* This trait is meant to be used only by functional JavaScript test classes.
*/
trait CKEditorTestTrait {
/**
* 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 = <<<JS
(function() {
return (
typeof CKEDITOR !== 'undefined'
&& typeof CKEDITOR.instances["$instance_id"] !== 'undefined'
&& CKEDITOR.instances["$instance_id"].instanceReady
);
}());
JS;
$this->getSession()->wait($timeout, $condition);
}
/**
* Assigns a name to the CKEditor iframe.
*
* @see \Behat\Mink\Session::switchToIFrame()
*/
protected function assignNameToCkeditorIframe() {
$javascript = <<<JS
(function(){
document.getElementsByClassName('cke_wysiwyg_frame')[0].id = 'ckeditor';
})()
JS;
$this->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'));
}
}

View File

@ -9,6 +9,7 @@ use Drupal\file\Entity\File;
use Drupal\filter\Entity\FilterFormat; use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\media\Entity\Media; use Drupal\media\Entity\Media;
use Drupal\Tests\ckeditor\Traits\CKEditorTestTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\TestFileCreationTrait; use Drupal\Tests\TestFileCreationTrait;
@ -18,6 +19,7 @@ use Drupal\Tests\TestFileCreationTrait;
*/ */
class CKEditorIntegrationTest extends WebDriverTestBase { class CKEditorIntegrationTest extends WebDriverTestBase {
use CKEditorTestTrait;
use MediaTypeCreationTrait; use MediaTypeCreationTrait;
use TestFileCreationTrait; use TestFileCreationTrait;
@ -668,20 +670,6 @@ JS;
$this->getSession()->executeScript($select_and_edit_caption); $this->getSession()->executeScript($select_and_edit_caption);
} }
/**
* Assigns a name to the CKEditor iframe.
*
* @see \Behat\Mink\Session::switchToIFrame()
*/
protected function assignNameToCkeditorIframe() {
$javascript = <<<JS
(function(){
document.getElementsByClassName('cke_wysiwyg_frame')[0].id = 'ckeditor';
})()
JS;
$this->getSession()->evaluateScript($javascript);
}
/** /**
* Assigns a name to the CKEditor context menu iframe. * Assigns a name to the CKEditor context menu iframe.
* *
@ -698,82 +686,6 @@ JS;
$this->getSession()->evaluateScript($javascript); $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 = <<<JS
(function() {
return (
typeof CKEDITOR !== 'undefined'
&& typeof CKEDITOR.instances["$instance_id"] !== 'undefined'
&& CKEDITOR.instances["$instance_id"].instanceReady
);
}());
JS;
$this->getSession()->wait($timeout, $condition);
}
/** /**
* Opens the context menu for the currently selected widget. * Opens the context menu for the currently selected widget.
* *

View File

@ -2,6 +2,14 @@
* @file media_library.module.css * @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 { .media-library-wrapper {
display: flex; display: flex;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -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);

View File

@ -0,0 +1,44 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function (Drupal, CKEDITOR) {
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;
Object.keys(attributes).forEach(function (key) {
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);

View File

@ -26,6 +26,8 @@ use Drupal\media_library\MediaLibraryState;
use Drupal\views\Form\ViewsForm; use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase; use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\ViewExecutable; use Drupal\views\ViewExecutable;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Component\Serialization\Json;
/** /**
* Implements hook_help(). * Implements hook_help().
@ -342,3 +344,73 @@ function _media_library_configure_view_display(MediaTypeInterface $type) {
]); ]);
return (bool) $display->save(); 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);
}
}
}
}

View File

@ -13,3 +13,6 @@ services:
media_library.opener.field_widget: media_library.opener.field_widget:
class: Drupal\media_library\MediaLibraryFieldWidgetOpener class: Drupal\media_library\MediaLibraryFieldWidgetOpener
arguments: ['@entity_type.manager'] arguments: ['@entity_type.manager']
media_library.opener.editor:
class: Drupal\media_library\MediaLibraryEditorOpener
arguments: ['@entity_type.manager']

View File

@ -0,0 +1,83 @@
<?php
namespace Drupal\media_library;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\editor\Ajax\EditorDialogSave;
/**
* The media library opener for text editors.
*
* @see \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary
*
* @internal
* This is an internal part of the media system in Drupal core and may be
* subject to change in minor releases. This class should not be
* instantiated or extended by external code.
*/
class MediaLibraryEditorOpener implements MediaLibraryOpenerInterface {
/**
* The text format entity storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $filterStorage;
/**
* The media storage.
*
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
*/
protected $mediaStorage;
/**
* The MediaLibraryEditorOpener constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->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;
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace Drupal\media_library\Plugin\CKEditorPlugin;
use Drupal\ckeditor\CKEditorPluginBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\editor\Entity\Editor;
use Drupal\media_library\MediaLibraryState;
use Drupal\media_library\MediaLibraryUiBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the "drupalmedialibrary" plugin.
*
* @CKEditorPlugin(
* id = "drupalmedialibrary",
* label = @Translation("Embed media from the Media Library"),
* )
*
* @internal
* Plugin classes are internal.
*/
class DrupalMediaLibrary extends CKEditorPluginBase implements ContainerFactoryPluginInterface {
/**
* The module extension list.
*
* @var \Drupal\Core\Extension\ModuleExtensionList
*/
protected $moduleExtensionList;
/**
* The media type entity storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $mediaTypeStorage;
/**
* Constructs a new DrupalMediaLibrary plugin object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
* The module extension list.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleExtensionList $extension_list_module, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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',
],
];
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\Component\Utility\Html;
use Drupal\editor\Entity\Editor;
use Drupal\file\Entity\File;
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;
/**
* @coversDefaultClass \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary
* @group media_library
*/
class CKEditorIntegrationTest extends WebDriverTestBase {
use CKEditorTestTrait;
use MediaTypeCreationTrait;
use TestFileCreationTrait;
/**
* The user to use during testing.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* The media item to embed.
*
* @var \Drupal\media\MediaInterface
*/
protected $media;
/**
* {@inheritdoc}
*/
protected static $modules = [
'ckeditor',
'media_library',
'node',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
FilterFormat::create([
'format' => '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');
}
}

View File

@ -31,6 +31,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
'media', 'media',
'media_library', 'media_library',
'media_library_test', 'media_library_test',
'filter',
'file', 'file',
'field', 'field',
'image', 'image',
@ -50,6 +51,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
$this->installSchema('file', 'file_usage'); $this->installSchema('file', 'file_usage');
$this->installSchema('system', ['sequences', 'key_value_expire']); $this->installSchema('system', ['sequences', 'key_value_expire']);
$this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test');
$this->installEntitySchema('filter_format');
$this->installEntitySchema('media'); $this->installEntitySchema('media');
$this->installConfig([ $this->installConfig([
'field', 'field',
@ -124,6 +126,76 @@ class MediaLibraryAccessTest extends KernelTestBase {
$this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']); $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. * Tests that the field widget opener respects entity-specific access.
*/ */