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
parent
e47700f70a
commit
6280478a1a
|
@ -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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
||||
|
@ -18,6 +19,7 @@ use Drupal\Tests\TestFileCreationTrait;
|
|||
*/
|
||||
class CKEditorIntegrationTest extends WebDriverTestBase {
|
||||
|
||||
use CKEditorTestTrait;
|
||||
use MediaTypeCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
|
@ -668,20 +670,6 @@ JS;
|
|||
$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.
|
||||
*
|
||||
|
@ -698,82 +686,6 @@ JS;
|
|||
$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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 283 B |
Binary file not shown.
After Width: | Height: | Size: 620 B |
|
@ -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);
|
|
@ -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);
|
|
@ -26,6 +26,8 @@ use Drupal\media_library\MediaLibraryState;
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
'media',
|
||||
'media_library',
|
||||
'media_library_test',
|
||||
'filter',
|
||||
'file',
|
||||
'field',
|
||||
'image',
|
||||
|
@ -50,6 +51,7 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$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 @@ class MediaLibraryAccessTest extends KernelTestBase {
|
|||
$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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue