From 65e117beffa134d9845ef5bc94376cfbe973243d Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sat, 6 Jan 2018 10:34:40 -0500 Subject: [PATCH 01/15] bdimaggio's initial work for #2801307. --- core/modules/editor/editor.module | 53 ++++ ...re.entity_form_mode.media.editor_embed.yml | 10 + .../mediaembed/icons/hidpi/mediaembed.png | Bin 0 -> 1414 bytes .../plugins/mediaembed/icons/mediaembed.png | Bin 0 -> 469 bytes .../media/js/plugins/mediaembed/plugin.js | 226 +++++++++++++++++ core/modules/media/media.module | 19 ++ .../src/Exception/MediaNotFoundException.php | 8 + .../Exception/RecursiveRenderingException.php | 8 + .../src/Plugin/CKEditorPlugin/MediaEmbed.php | 100 ++++++++ .../src/Plugin/Filter/MediaEmbedFilter.php | 237 ++++++++++++++++++ .../media/templates/media-embed.html.twig | 15 ++ .../tests/src/Kernel/MediaFilterTest.php | 20 ++ 12 files changed, 696 insertions(+) create mode 100644 core/modules/media/config/install/core.entity_form_mode.media.editor_embed.yml create mode 100644 core/modules/media/js/plugins/mediaembed/icons/hidpi/mediaembed.png create mode 100644 core/modules/media/js/plugins/mediaembed/icons/mediaembed.png create mode 100644 core/modules/media/js/plugins/mediaembed/plugin.js create mode 100644 core/modules/media/src/Exception/MediaNotFoundException.php create mode 100644 core/modules/media/src/Exception/RecursiveRenderingException.php create mode 100644 core/modules/media/src/Plugin/CKEditorPlugin/MediaEmbed.php create mode 100644 core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php create mode 100644 core/modules/media/templates/media-embed.html.twig create mode 100644 core/modules/media/tests/src/Kernel/MediaFilterTest.php diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index f3bb1b531418..d704dd06a6ec 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\Html; +use Drupal\Core\Entity\Entity\EntityFormMode; use Drupal\editor\Entity\Editor; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; @@ -16,6 +17,8 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Entity\EntityInterface; use Drupal\filter\FilterFormatInterface; use Drupal\filter\Plugin\FilterInterface; +use Drupal\media\Entity\Media; +use Drupal\media\Entity\MediaType; /** * Implements hook_help(). @@ -252,6 +255,56 @@ function editor_form_filter_admin_format_submit($form, FormStateInterface $form_ } } +/** + * Implements hook_form_FORM_ID_alter(). + */ +function editor_form_media_type_add_form_alter(&$form, &$form_state, $form_id) { + // Add this submit function to the submit _button_ rather than the form's + // '#submit' array, because the latter approach loses our submit function when + // the form is refreshed via ajax (e.g. when you have to choose a source field + // for your media type). + $form['actions']['submit']['#submit'][] = '_editor_add_media_embed_display'; +} + +/** + * A submit function added to the form that adds a media type. This will create + * a form display, containing only this media type's required fields, to be used + * when a user embeds media in the editor. + * + * @param array $form + * The media-type-addition form. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * That form's state. + * + * @internal + */ +function _editor_add_media_embed_display(array &$form, FormStateInterface $form_state) { + $entity_type = 'media'; + $bundle = $form_state->getValue('id'); + $form_mode = 'editor_embed'; + + // Create and enable the form display for this new media type, using the + // editor_embed form mode created during Editor module install. + $form_display = entity_get_form_display($entity_type, $bundle, $form_mode); + + // Show only the required fields; hide all others. + /** @var \Drupal\Core\Field\BaseFieldDefinition $field_definition */ + foreach (\Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle) as $field_definition) { + $field_name = $field_definition->getName(); + if ($field_definition->isRequired()) { + $form_display->setComponent($field_name, []); + } + else { + $form_display->removeComponent($field_name); + } + } + + // Enable and save the cleaned-up form display. + $form_display->set('status', TRUE); + $form_display->save(); +} + /** * Loads an individual configured text editor based on text format ID. * diff --git a/core/modules/media/config/install/core.entity_form_mode.media.editor_embed.yml b/core/modules/media/config/install/core.entity_form_mode.media.editor_embed.yml new file mode 100644 index 000000000000..96d67e2790c2 --- /dev/null +++ b/core/modules/media/config/install/core.entity_form_mode.media.editor_embed.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - editor + - media +id: media.editor_embed +label: Editor embed +targetEntityType: media +cache: true diff --git a/core/modules/media/js/plugins/mediaembed/icons/hidpi/mediaembed.png b/core/modules/media/js/plugins/mediaembed/icons/hidpi/mediaembed.png new file mode 100644 index 0000000000000000000000000000000000000000..19f663a7b917ab3db4db2242350003d0ffde53c6 GIT binary patch literal 1414 zcmV;11$p|3P)y(XF8I(@ugp~Tu0o8C0&NLKukpa16nZ_jtmcg}zQ zi}-bXoc>SPr~<@{2mWx z!_6GaDmf2hLEBNk8C!|+`cVJ{6f>IQ+6VHi$|Ku_=Lw-e~pi84_PU#P#~S24zTaA z+o?4WAl+swf=gKXfDnc;g&lBuz2x9p0Qrjt4qWZ&>LTFhnVGA<&(Bj+ID9=T1fhU- z0tef!j)CARcq|G4=u9M)SYKbKbUOX#k9Y1IYGm1f z3_F9h)ofP2TMZB>JXG1tkZZ$P3@eWb<4qwW$G{7jOol!j8;c%4apL=#nVDG(JY#Kz z_oR_3avMwRZD>HTVc;2SGFl5yRSHa_*vxSwVqAXz!iCRykOZpn9W*Js-HygmMecLE zDHIBk=2|%*HKq>dFKdX$!~#dj zba*)8;BNPLdHG{j<2QYJ`lppgkH{`hfi8asD3|~p8`1%l zCB~U`a~_)`5qe?&eu~9n#8Hvo??(z?00Tyi%S>el7#|!P7#KpI*S5B(|L|dlHxRfb z5{ZbfxtZ)Pmqi6p)d}S@oInJSwHY~0CeIPk8$8alq)W?7OO(y!V8|d3uyGNKos1tm zIOb#@@QpE?N;Q9X_wKi%t7}l7pU>lzXvLyggi2)}z#w^yJZ!kRr3K{i$i96S z2L=aEt4`-HEa~Y$dpkYPq;0HFu(g#$PfwKRFJ9{G6a`I#%;t$g`d&|^4iV9(#LnTH z!;oJN8~?GKPk4y7xu>y_SUMNC=80E$hOBRnjs_>Erzb=_{*9r=!orehZLP2Ocy7Jc z*XPqfUUAU?aK_ESB>Vc_7NOAe@aQP5Ak~&uEENz~U~))r=Ug)`4IDWVXDhT-C>Zbc zC6n}AByyVXhC#7+?|ClDk-`4{gd&d^2rD7gz;7tKvhopXrs@;`@EqhSpJflv9Ob!} z6N$472Iry zP*CA;yrsSUVw7`Tl#;O2yO|v&|(%g+3r0+7o z32vnX`MGcJHoysP8*Vnm;lS-Dio6KfYYMb_=CkpU2exh^ZyLSADh1ju>*2S|Ms Uh}<2|aBy^VbaHZXadB~XclY%4^z!oZ_V)Ji@$vKX^Y`};2nYxa3=9ei3Jnd7jEpS5 z_lO;6w?avfUoZm)2bZX#mQO%vLP=TOoCT}au0414_S0w2UcGw%>FbYw2W`FUfogj_ zT^vIsBwG)Li#H}PG&~I7_GaQ)aji{8-eKSV|979Wm^-#o?uS1ML-#p;?in60v!Xur zG6<)xe_+heoV2#VpZV7b-kJRsY$Xm)wQs#qGhh@A`pL2I(&wvPXBm4Vs$>1r`1^lv zO=5gpaC3L%<)=?eZ1U3^X5QX$?p&$t?QP-C4jj(c_p#YBmM}4BHkeEM3q1`kx2gg< OjKR~@&t;ucLK6VbLyPPH literal 0 HcmV?d00001 diff --git a/core/modules/media/js/plugins/mediaembed/plugin.js b/core/modules/media/js/plugins/mediaembed/plugin.js new file mode 100644 index 000000000000..50a88ccbe139 --- /dev/null +++ b/core/modules/media/js/plugins/mediaembed/plugin.js @@ -0,0 +1,226 @@ +/** + * 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('mediaembed', { + // This plugin requires the Widgets System defined in the 'widget' plugin. + requires: 'widget', + + // The plugin initialization logic goes inside this method. + beforeInit: function (editor) { + // Configure CKEditor DTD for custom drupal-entity element. + // @see https://www.drupal.org/node/2448449#comment-9717735 + var dtd = CKEDITOR.dtd, tagName; + dtd['drupal-entity'] = {'#': 1}; + // Register drupal-entity element as allowed child, in each tag that can + // contain a div element. + for (tagName in dtd) { + if (dtd[tagName].div) { + dtd[tagName]['drupal-entity'] = 1; + } + } + + // Generic command for adding/editing entities of all types. + editor.addCommand('editdrupalentity', { + allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]', + requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]', + modes: { wysiwyg : 1 }, + canUndo: true, + exec: function (editor, data) { + data = data || {}; + + var existingElement = getSelectedEmbeddedEntity(editor); + + var existingValues = {}; + if (existingElement && existingElement.$ && existingElement.$.firstChild) { + var embedDOMElement = existingElement.$.firstChild; + // Populate array with the entity's current attributes. + var attribute = null, attributeName; + for (var key = 0; key < embedDOMElement.attributes.length; key++) { + attribute = embedDOMElement.attributes.item(key); + attributeName = attribute.nodeName.toLowerCase(); + if (attributeName.substring(0, 15) === 'data-cke-saved-') { + continue; + } + existingValues[attributeName] = existingElement.data('cke-saved-' + attributeName) || attribute.nodeValue; + } + } + + var embed_button_id = data.id ? data.id : existingValues['data-embed-button']; + + var dialogSettings = { + dialogClass: 'entity-select-dialog', + resizable: false + }; + + var saveCallback = function (values) { + var entityElement = editor.document.createElement('drupal-entity'); + var attributes = values.attributes; + for (var key in attributes) { + entityElement.setAttribute(key, attributes[key]); + } + + editor.insertHtml(entityElement.getOuterHtml()); + if (existingElement) { + // Detach the behaviors that were attached when the entity content + // was inserted. + Drupal.runEmbedBehaviors('detach', existingElement.$); + existingElement.remove(); + } + }; + + // Open the entity embed dialog for corresponding EmbedButton. + Drupal.ckeditor.openDialog(editor, Drupal.url('entity-embed/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings); + } + }); + + // Register the entity embed widget. + editor.widgets.add('drupalentity', { + // Minimum HTML which is required by this widget to work. + allowedContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]', + requiredContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]', + + // Simply recognize the element as our own. The inner markup if fetched + // and inserted the init() callback, since it requires the actual DOM + // element. + upcast: function (element) { + var attributes = element.attributes; + if (attributes['data-entity-type'] === undefined || (attributes['data-entity-id'] === undefined && attributes['data-entity-uuid'] === undefined) || (attributes['data-view-mode'] === undefined && attributes['data-entity-embed-display'] === undefined)) { + return; + } + // Generate an ID for the element, so that we can use the Ajax + // framework. + element.attributes.id = generateEmbedId(); + return element; + }, + + // Fetch the rendered entity. + init: function () { + /** @type {CKEDITOR.dom.element} */ + var element = this.element; + // Use the Ajax framework to fetch the HTML, so that we can retrieve + // out-of-band assets (JS, CSS...). + var entityEmbedPreview = Drupal.ajax({ + base: element.getId(), + element: element.$, + url: Drupal.url('embed/preview/' + editor.config.drupal.format + '?' + $.param({ + value: element.getOuterHtml() + })), + progress: {type: 'none'}, + // Use a custom event to trigger the call. + event: 'entity_embed_dummy_event' + }); + entityEmbedPreview.execute(); + }, + + // Downcast the element. + downcast: function (element) { + // Only keep the wrapping element. + element.setHtml(''); + // Remove the auto-generated ID. + delete element.attributes.id; + return element; + } + }); + + // Register the toolbar buttons. + if (editor.ui.addButton) { + for (var key in editor.config.MediaEmbed_buttons) { + var button = editor.config.MediaEmbed_buttons[key]; + editor.ui.addButton(button.id, { + label: button.label, + data: button, + allowedContent: 'drupal-entity[!data-entity-type,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-display-settings,!data-align,!data-caption,!data-embed-button]', + click: function(editor) { + editor.execCommand('editdrupalentity', this.data); + }, + icon: button.image + }); + } + } + + // Register context menu option for editing widget. + if (editor.contextMenu) { + editor.addMenuGroup('drupalentity'); + editor.addMenuItem('drupalentity', { + label: Drupal.t('Edit Entity'), + icon: this.path + 'entity.png', + command: 'editdrupalentity', + group: 'drupalentity' + }); + + editor.contextMenu.addListener(function(element) { + if (isEditableEntityWidget(editor, element)) { + return { drupalentity: CKEDITOR.TRISTATE_OFF }; + } + }); + } + + // Execute widget editing action on double click. + editor.on('doubleclick', function (evt) { + var element = getSelectedEmbeddedEntity(editor) || evt.data.element; + + if (isEditableEntityWidget(editor, element)) { + editor.execCommand('editdrupalentity'); + } + }); + } + }); + + /** + * Get the surrounding drupalentity widget element. + * + * @param {CKEDITOR.editor} editor + */ + function getSelectedEmbeddedEntity(editor) { + var selection = editor.getSelection(); + var selectedElement = selection.getSelectedElement(); + if (isEditableEntityWidget(editor, selectedElement)) { + return selectedElement; + } + + return null; + } + + /** + * Checks if the given element is an editable drupalentity widget. + * + * @param {CKEDITOR.editor} editor + * @param {CKEDITOR.htmlParser.element} element + */ + function isEditableEntityWidget (editor, element) { + var widget = editor.widgets.getByElement(element, true); + if (!widget || widget.name !== 'drupalentity') { + return false; + } + + var button = $(element.$.firstChild).attr('data-embed-button'); + if (!button) { + // If there was no data-embed-button attribute, not editable. + return false; + } + + // The button itself must be valid. + return editor.config.DrupalEntity_buttons.hasOwnProperty(button); + } + + /** + * Generates unique HTML IDs for the widgets. + * + * @returns {string} + */ + function generateEmbedId() { + if (typeof generateEmbedId.counter == 'undefined') { + generateEmbedId.counter = 0; + } + return 'entity-embed-' + generateEmbedId.counter++; + } + +})(jQuery, Drupal, CKEDITOR); diff --git a/core/modules/media/media.module b/core/modules/media/media.module index 296945973ae5..c0891e76b88f 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -66,6 +66,9 @@ function media_theme() { 'media' => [ 'render element' => 'elements', ], + 'media_embed' => [ + 'render element' => 'element', + ], ]; } @@ -172,3 +175,19 @@ function media_form_field_ui_field_storage_add_form_alter(&$form, FormStateInter $form['add']['new_storage_type']['#weight'] = 0; $form['add']['description_wrapper']['#weight'] = 1; } + +/** + * Prepares variables for the media embed template. + * + * Default template: media-embed.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + * Properties used: #attributes, #children. + */ + function template_preprocess_media_embed(&$variables) { + $variables['element'] += ['#attributes' => []]; + $variables['attributes'] = $variables['element']['#attributes']; + $variables['children'] = $variables['element']['#children']; + } diff --git a/core/modules/media/src/Exception/MediaNotFoundException.php b/core/modules/media/src/Exception/MediaNotFoundException.php new file mode 100644 index 000000000000..201397b57235 --- /dev/null +++ b/core/modules/media/src/Exception/MediaNotFoundException.php @@ -0,0 +1,8 @@ +entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('entity_type.bundle.info'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return [ + 'MediaEmbed_dialogTitleAdd' => $this->t("Add Media"), + 'MediaEmbed_dialogTitleEdit' => $this->t("Edit Media"), + 'MediaEmbed_buttons' => $this->getButtons(), + ]; + } + + /** + * {@inheritdoc} + */ + public function getFile() { + return drupal_get_path('module', 'media') . '/js/plugins/mediaembed/plugin.js'; + } + + /** + * (@inheritdoc} + */ + public function getButtons() { + if ($this->buttons) { + return $this->buttons; + } + + $buttons = []; + foreach ($this->entityTypeBundleInfo->getBundleInfo('media') as $machine_name => $media_type) { + $buttons[$machine_name] = [ + 'label' => $media_type['label'], + 'id' => $machine_name, + 'image' => base_path() . drupal_get_path('module', 'media') . '/js/plugins/mediaembed/icons/mediaembed.png', + ]; + } + $this->buttons = $buttons; + return $buttons; + } + +} diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php new file mode 100644 index 000000000000..694c04636495 --- /dev/null +++ b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php @@ -0,0 +1,237 @@ +entityTypeManager = $entity_type_manager; + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('renderer') + ); + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode) { + $result = new FilterProcessResult($text); + + if (strpos($text, 'data-entity-type') !== FALSE && (strpos($text, 'data-entity-embed-display') !== FALSE || strpos($text, 'data-view-mode') !== FALSE)) { + $dom = Html::load($text); + $xpath = new \DOMXPath($dom); + + foreach ($xpath->query('//drupal-entity[@data-entity-type and (@data-entity-uuid or @data-entity-id) and (@data-entity-embed-display or @data-view-mode)]') as $node) { + /** @var \DOMElement $node */ + $entity_type = $node->getAttribute('data-entity-type'); + $entity = NULL; + $entity_output = ''; + + // data-entity-embed-settings is deprecated, make sure we convert it to + // data-entity-embed-display-settings. + if (($settings = $node->getAttribute('data-entity-embed-settings')) && !$node->hasAttribute('data-entity-embed-display-settings')) { + $node->setAttribute('data-entity-embed-display-settings', $settings); + $node->removeAttribute('data-entity-embed-settings'); + } + + try { + // Load the entity either by UUID (preferred) or ID. + $id = NULL; + $entity = NULL; + if ($id = $node->getAttribute('data-entity-uuid')) { + $entity = $this->entityTypeManager->getStorage($entity_type) + ->loadByProperties(['uuid' => $id]); + $entity = current($entity); + } + else { + $id = $node->getAttribute('data-entity-id'); + $entity = $this->entityTypeManager->getStorage($entity_type)->load($id); + } + + if ($entity) { + // Protect ourselves from recursive rendering. + static $depth = 0; + $depth++; + if ($depth > 20) { + throw new RecursiveRenderingException(sprintf('Recursive rendering detected when rendering embedded %s entity %s.', $entity_type, $entity->id())); + } + + // If a UUID was not used, but is available, add it to the HTML. + if (!$node->getAttribute('data-entity-uuid') && $uuid = $entity->uuid()) { + $node->setAttribute('data-entity-uuid', $uuid); + } + + $context = $this->getNodeAttributesAsArray($node); + $context += array('data-langcode' => $langcode); + $build = $this->buildRenderArray($entity, $context); + // We need to render the embedded entity: + // - without replacing placeholders, so that the placeholders are + // only replaced at the last possible moment. Hence we cannot use + // either renderPlain() or renderRoot(), so we must use render(). + // - without bubbling beyond this filter, because filters must + // ensure that the bubbleable metadata for the changes they make + // when filtering text makes it onto the FilterProcessResult + // object that they return ($result). To prevent that bubbling, we + // must wrap the call to render() in a render context. + $entity_output = $this->renderer->executeInRenderContext(new RenderContext(), function () use (&$build) { + return $this->renderer->render($build); + }); + $result = $result->merge(BubbleableMetadata::createFromRenderArray($build)); + + $depth--; + } + else { + throw new MediaNotFoundException(sprintf('Unable to load embedded %s media entity %s.', $entity_type, $id)); + } + } + catch (\Exception $e) { + watchdog_exception('media_embed', $e); + } + + $this->replaceNodeContent($node, $entity_output); + } + + $result->setProcessedText(Html::serialize($dom)); + } + + return $result; + } + + /** + * Build a render array for the media entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be rendered. + * @param array $context + * (optional) Array of context values, corresponding to the attributes on + * the embed HTML tag. + * + * @return array + * A render array. + */ + public function buildRenderArray(EntityInterface $entity, array $context = []) { + // Merge in default attributes. + $context += [ + 'data-entity-type' => $entity->getEntityTypeId(), + 'data-entity-uuid' => $entity->uuid(), + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => [], + ]; + + // The caption text is double-encoded, so decode it here. + if (isset($context['data-caption'])) { + $context['data-caption'] = Html::decodeEntities($context['data-caption']); + } + + // Build and render the Entity Embed Display plugin, allowing modules to + // alter the result before rendering. + $build = [ + '#theme_wrappers' => ['media_embed'], + '#attributes' => ['class' => ['embedded-media']], + '#entity' => $entity, + '#context' => $context, + ]; + $build += $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, 'default'); + // We're providing embed-specific theming, so don't use media's standard. + unset($build['#theme']); + + // Maintain data-align if it is there. + if (isset($context['data-align'])) { + $build['#attributes']['data-align'] = $context['data-align']; + } + elseif ((isset($context['class']))) { + $build['#attributes']['class'][] = $context['class']; + } + + // Maintain data-caption if it is there. + if (isset($context['data-caption'])) { + $build['#attributes']['data-caption'] = $context['data-caption']; + } + + // Make sure that access to the entity is respected. + $build['#access'] = $entity->access('view', NULL, TRUE); + + return $build; + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + if ($long) { + return $this->t(' +

You can embed entities. Additional properties can be added to the embed tag like data-caption and data-align if supported. Example:

+ <drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" />'); + } + else { + return $this->t('You can embed entities.'); + } + } + +} diff --git a/core/modules/media/templates/media-embed.html.twig b/core/modules/media/templates/media-embed.html.twig new file mode 100644 index 000000000000..284d2d116c34 --- /dev/null +++ b/core/modules/media/templates/media-embed.html.twig @@ -0,0 +1,15 @@ +{# +/** + * @file + * Default theme implementation of a container used to wrap embedded entities. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - children: The rendered child elements of the container. + * + * @see template_preprocess_media_embed() + * + * @ingroup themeable + */ +#} +{{ children }} diff --git a/core/modules/media/tests/src/Kernel/MediaFilterTest.php b/core/modules/media/tests/src/Kernel/MediaFilterTest.php new file mode 100644 index 000000000000..7b0a782853fc --- /dev/null +++ b/core/modules/media/tests/src/Kernel/MediaFilterTest.php @@ -0,0 +1,20 @@ +createMediaType('image'); + $filepath = \Drupal::root() . '/' . drupal_get_path('module', 'media') . '/tests/fixtures/example_1.jpeg'; + $media = $this->generateMedia('test.patch', $mediaType); + + } + +} From e12de9b6e7e60469f7f94e81814198c776e8cfe5 Mon Sep 17 00:00:00 2001 From: Ben Di Maggio Date: Sat, 6 Jan 2018 11:53:29 -0500 Subject: [PATCH 02/15] Removing inadvertent reliance on Embed module. --- .../src/Plugin/Filter/MediaEmbedFilter.php | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php index 694c04636495..99dc13d8563d 100644 --- a/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php +++ b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php @@ -11,7 +11,6 @@ use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\embed\DomHelperTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\media\Exception\MediaNotFoundException; use Drupal\media\Exception\RecursiveRenderingException; @@ -28,8 +27,6 @@ */ class MediaEmbedFilter extends FilterBase implements ContainerFactoryPluginInterface { - use DomHelperTrait; - /** * The renderer service. * @@ -220,6 +217,34 @@ public function buildRenderArray(EntityInterface $entity, array $context = []) { return $build; } + /** + * Replace the contents of a DOMNode. + * + * @param \DOMNode $node + * A DOMNode object. + * @param string $content + * The text or HTML that will replace the contents of $node. + */ + protected function replaceNodeContent(\DOMNode &$node, $content) { + if (strlen($content)) { + // Load the content into a new DOMDocument and retrieve the DOM nodes. + $replacement_nodes = Html::load($content)->getElementsByTagName('body') + ->item(0) + ->childNodes; + } + else { + $replacement_nodes = [$node->ownerDocument->createTextNode('')]; + } + + foreach ($replacement_nodes as $replacement_node) { + // Import the replacement node from the new DOMDocument into the original + // one, importing also the child nodes of the replacement node. + $replacement_node = $node->ownerDocument->importNode($replacement_node, TRUE); + $node->parentNode->insertBefore($replacement_node, $node); + } + $node->parentNode->removeChild($node); + } + /** * {@inheritdoc} */ From 3faaf34bd89388a26aeae451204d84779a079c41 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 10:35:08 -0500 Subject: [PATCH 03/15] Update path for dialog button. --- core/modules/media/js/plugins/mediaembed/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/media/js/plugins/mediaembed/plugin.js b/core/modules/media/js/plugins/mediaembed/plugin.js index 50a88ccbe139..ac72b94aab2a 100644 --- a/core/modules/media/js/plugins/mediaembed/plugin.js +++ b/core/modules/media/js/plugins/mediaembed/plugin.js @@ -77,7 +77,7 @@ }; // Open the entity embed dialog for corresponding EmbedButton. - Drupal.ckeditor.openDialog(editor, Drupal.url('entity-embed/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings); + Drupal.ckeditor.openDialog(editor, Drupal.url('media/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings); } }); From 08922ffa72fff4aae3505688b247b4a7d55e6c50 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 10:36:33 -0500 Subject: [PATCH 04/15] Add route for media embed ajax callback. --- core/modules/media/media.routing.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml index 9fbadeff2947..b09d07a6e7ae 100644 --- a/core/modules/media/media.routing.yml +++ b/core/modules/media/media.routing.yml @@ -19,3 +19,13 @@ entity.media.revision: requirements: _access_media_revision: 'view' media: \d+ + +media.embed_dialog: + path: '/media/dialog/{editor}/{media_type}' + defaults: + _controller: '\Drupal\media\Controller\MediaEmbedDialog::form' + _title: 'Embed entity' + requirements: + _permission: 'update any media' + options: + _theme: ajax_base_page From 9a03aacefe2b4e62ad658f3036f32e2465048790 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 10:37:36 -0500 Subject: [PATCH 05/15] Add embed entity form and controller for modal. --- .../media/src/Controller/MediaEmbedDialog.php | 15 +++ core/modules/media/src/Entity/Media.php | 1 + .../modules/media/src/Form/MediaFormEmbed.php | 107 ++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 core/modules/media/src/Controller/MediaEmbedDialog.php create mode 100644 core/modules/media/src/Form/MediaFormEmbed.php diff --git a/core/modules/media/src/Controller/MediaEmbedDialog.php b/core/modules/media/src/Controller/MediaEmbedDialog.php new file mode 100644 index 000000000000..461a2d7816f8 --- /dev/null +++ b/core/modules/media/src/Controller/MediaEmbedDialog.php @@ -0,0 +1,15 @@ + 'file', 'uid' => 1]); + $form = $this->entityFormBuilder()->getForm($entity, 'add_embed'); + return $form; + } +} \ No newline at end of file diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php index 6e5877193884..a981ad2d6f8b 100644 --- a/core/modules/media/src/Entity/Media.php +++ b/core/modules/media/src/Entity/Media.php @@ -38,6 +38,7 @@ * "add" = "Drupal\media\MediaForm", * "edit" = "Drupal\media\MediaForm", * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm", + * "add_embed" = "Drupal\media\Form\MediaFormEmbed", * }, * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\media\MediaViewsData", diff --git a/core/modules/media/src/Form/MediaFormEmbed.php b/core/modules/media/src/Form/MediaFormEmbed.php new file mode 100644 index 000000000000..856447ad9284 --- /dev/null +++ b/core/modules/media/src/Form/MediaFormEmbed.php @@ -0,0 +1,107 @@ + '::ajaxFormRebuild', + ]; + $form['#prefix'] = '
'; + $form['#suffix'] = '
'; + return $form; + } + + /** + * {@inheritdoc} + */ + public function ajaxFormRebuild($form, FormStateInterface $form_state) { + + $response = new AjaxResponse(); + + // Display errors in form, if any. + if ($form_state->hasAnyErrors()) { + unset($form['#prefix'], $form['#suffix']); + $form['status_messages'] = array( + '#type' => 'status_messages', + '#weight' => -10, + ); + $response->addCommand(new HtmlCommand('#entity-embed-dialog-form', $form)); + } + else { + // Serialize entity embed settings to JSON string. + + $values = $form_state->getValues(); + + $values['attributes'] = [ + 'data-embed-button' => 'media', + 'data-entity-embed-display' => 'view_mode:media.full', + 'data-entity-type' => 'media', + 'data-entity-uuid' => $this->getEntity()->uuid(), + ]; + if (!empty($values['attributes']['data-entity-embed-display-settings'])) { + $values['attributes']['data-entity-embed-display-settings'] = Json::encode($values['attributes']['data-entity-embed-display-settings']); + } + + // Filter out empty attributes. + $values['attributes'] = array_filter($values['attributes'], function($value) { + return (bool) Unicode::strlen((string) $value); + }); + + // Allow other modules to alter the values before getting submitted to the WYSIWYG. + $this->moduleHandler->alter('entity_embed_values', $values, $entity, $display, $form_state); + + $response->addCommand(new EditorDialogSave($values)); + $response->addCommand(new CloseModalDialogCommand()); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $saved = parent::save($form, $form_state); + $context = ['@type' => $this->entity->bundle(), '%label' => $this->entity->label()]; + $logger = $this->logger('media'); + $t_args = ['@type' => $this->entity->bundle->entity->label(), '%label' => $this->entity->label()]; + + if ($saved === SAVED_NEW) { + $logger->notice('@type: added %label.', $context); + drupal_set_message($this->t('@type %label has been created.', $t_args)); + } + else { + $logger->notice('@type: updated %label.', $context); + drupal_set_message($this->t('@type %label has been updated.', $t_args)); + } + + // $form_state->setRedirectUrl($this->entity->toUrl('canonical')); + return $saved; + } + + public function actionsElement(array $form, FormStateInterface $form_state) { + $element = parent::actionsElement($form, $form_state); + $element['submit']['#ajax'] = [ + 'callback' => '::ajaxFormRebuild', + ]; + return $element; + } + +} From 099938a48d3c3da48447509b1ea23dc211a36df7 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 12:28:27 -0500 Subject: [PATCH 06/15] Rename embed form operation the same as the name form display. --- core/modules/media/src/Controller/MediaEmbedDialog.php | 4 ++-- core/modules/media/src/Entity/Media.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/modules/media/src/Controller/MediaEmbedDialog.php b/core/modules/media/src/Controller/MediaEmbedDialog.php index 461a2d7816f8..073a4b11fe15 100644 --- a/core/modules/media/src/Controller/MediaEmbedDialog.php +++ b/core/modules/media/src/Controller/MediaEmbedDialog.php @@ -9,7 +9,7 @@ class MediaEmbedDialog extends ControllerBase { public function form() { $entity = Media::create(['bundle' => 'file', 'uid' => 1]); - $form = $this->entityFormBuilder()->getForm($entity, 'add_embed'); + $form = $this->entityFormBuilder()->getForm($entity, 'editor_embed'); return $form; } -} \ No newline at end of file +} diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php index a981ad2d6f8b..c17f45a836e8 100644 --- a/core/modules/media/src/Entity/Media.php +++ b/core/modules/media/src/Entity/Media.php @@ -38,7 +38,7 @@ * "add" = "Drupal\media\MediaForm", * "edit" = "Drupal\media\MediaForm", * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm", - * "add_embed" = "Drupal\media\Form\MediaFormEmbed", + * "editor_embed" = "Drupal\media\Form\MediaFormEmbed", * }, * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\media\MediaViewsData", From 6b8ff4782837083b0f14918967d7135ed97fb9e7 Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sat, 6 Jan 2018 15:05:56 -0500 Subject: [PATCH 07/15] Update media embed filter test. --- .../tests/src/Kernel/MediaEmbedFilterTest.php | 77 +++++++++++++++++++ .../tests/src/Kernel/MediaFilterTest.php | 20 ----- 2 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php delete mode 100644 core/modules/media/tests/src/Kernel/MediaFilterTest.php diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php new file mode 100644 index 000000000000..fab9809a8d12 --- /dev/null +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -0,0 +1,77 @@ +container->get('plugin.manager.filter'); + $bag = new FilterPluginCollection($manager, []); + $this->filters = $bag->getAll(); + } + + public function testMediaEmbedFilter() { + $filter = $this->filters['media_embed']; + + $test = function ($input) use ($filter) { + return $filter->process($input, 'und'); + }; + + // Create media type, media entity, and rendered entity. + $mediaType = $this->createMediaType('image'); + $media = $this->generateMedia('test.patch', $mediaType); + $data_entity_uuid = $media->uuid(); + $build = $this->entityTypeManager->getViewBuilder($media->getEntityTypeId())->view($media, 'default'); + $rendered_media = $this->renderer->render($build); + + // Test data-entity-embed-display attribute. + $input = ''; + $expected = $rendered_media; + $this->assertSame($expected, $test($input)->getProcessedText()); + + // Test data-view-mode attribute. + $input = ''; + $expected = $rendered_media; + $this->assertSame($expected, $test($input)->getProcessedText()); + } + +} diff --git a/core/modules/media/tests/src/Kernel/MediaFilterTest.php b/core/modules/media/tests/src/Kernel/MediaFilterTest.php deleted file mode 100644 index 7b0a782853fc..000000000000 --- a/core/modules/media/tests/src/Kernel/MediaFilterTest.php +++ /dev/null @@ -1,20 +0,0 @@ -createMediaType('image'); - $filepath = \Drupal::root() . '/' . drupal_get_path('module', 'media') . '/tests/fixtures/example_1.jpeg'; - $media = $this->generateMedia('test.patch', $mediaType); - - } - -} From fe86d377a2b43a5e43b5735109fff69233782312 Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sat, 6 Jan 2018 16:34:33 -0500 Subject: [PATCH 08/15] Clean up entity creation in embed filter test. --- .../tests/src/Kernel/MediaEmbedFilterTest.php | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php index fab9809a8d12..8c2683ad1341 100644 --- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -3,8 +3,7 @@ namespace Drupal\Tests\media\Kernel; use Drupal\filter\FilterPluginCollection; -use Drupal\Core\Render\RendererInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\media\Entity\Media; /** * Tests the media content input filter. @@ -25,20 +24,6 @@ class MediaEmbedFilterTest extends MediaKernelTestBase { */ public static $modules = ['filter']; - /** - * The renderer service. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; - - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - /** * {@inheritdoc} */ @@ -49,6 +34,9 @@ protected function setUp() { $this->filters = $bag->getAll(); } + /** + * Tests the media embed filter. + */ public function testMediaEmbedFilter() { $filter = $this->filters['media_embed']; @@ -56,20 +44,24 @@ public function testMediaEmbedFilter() { return $filter->process($input, 'und'); }; - // Create media type, media entity, and rendered entity. - $mediaType = $this->createMediaType('image'); - $media = $this->generateMedia('test.patch', $mediaType); - $data_entity_uuid = $media->uuid(); - $build = $this->entityTypeManager->getViewBuilder($media->getEntityTypeId())->view($media, 'default'); - $rendered_media = $this->renderer->render($build); + // Create media entity. + $media = Media::create(['bundle' => $this->testMediaType->id()]); + $media->save(); + $media_uuid = $media->uuid(); + + // Render entity. + $build = entity_view($media, 'default', 'und'); + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + $rendered_media = $renderer->renderPlain($build); // Test data-entity-embed-display attribute. - $input = ''; + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); // Test data-view-mode attribute. - $input = ''; + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); } From ae3fdab77cf0405ff07f0944d31892f950b1a8c1 Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sun, 7 Jan 2018 13:49:36 -0500 Subject: [PATCH 09/15] Fix expected rendering. --- .../tests/src/Kernel/MediaEmbedFilterTest.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php index 8c2683ad1341..10b2d6be382c 100644 --- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -38,8 +38,10 @@ protected function setUp() { * Tests the media embed filter. */ public function testMediaEmbedFilter() { + // Get media embed filter. $filter = $this->filters['media_embed']; + // Create test function. $test = function ($input) use ($filter) { return $filter->process($input, 'und'); }; @@ -50,18 +52,20 @@ public function testMediaEmbedFilter() { $media_uuid = $media->uuid(); // Render entity. - $build = entity_view($media, 'default', 'und'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $view_builder */ + $view_builder = \Drupal::entityTypeManager(); + $build = $view_builder->getViewBuilder($media->getEntityTypeId())->view($media); /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); - $rendered_media = $renderer->renderPlain($build); + $rendered_media = $renderer->renderPlain($build)->__toString(); - // Test data-entity-embed-display attribute. - $input = ''; + // Test filter using data-entity-embed-display attribute. + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); - // Test data-view-mode attribute. - $input = ''; + // Test filter using data-view-mode attribute. + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); } From 5ab69bec4b7b08b5111acd28bc02db50da42c0ac Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sun, 7 Jan 2018 15:07:46 -0500 Subject: [PATCH 10/15] It would help to use the right media type. --- core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php index 10b2d6be382c..c87c884bb99e 100644 --- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -60,12 +60,12 @@ public function testMediaEmbedFilter() { $rendered_media = $renderer->renderPlain($build)->__toString(); // Test filter using data-entity-embed-display attribute. - $input = ''; + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); // Test filter using data-view-mode attribute. - $input = ''; + $input = ''; $expected = $rendered_media; $this->assertSame($expected, $test($input)->getProcessedText()); } From 0ac1f64f1cfd59532cc5b9371580528ebef2dbc7 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 15:31:49 -0500 Subject: [PATCH 11/15] Remove hardcoded create args. --- core/modules/media/src/Controller/MediaEmbedDialog.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/modules/media/src/Controller/MediaEmbedDialog.php b/core/modules/media/src/Controller/MediaEmbedDialog.php index 073a4b11fe15..8e1f558ec5d5 100644 --- a/core/modules/media/src/Controller/MediaEmbedDialog.php +++ b/core/modules/media/src/Controller/MediaEmbedDialog.php @@ -3,13 +3,17 @@ namespace Drupal\media\Controller; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Routing\RequestContext; use Drupal\media\Entity\Media; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; class MediaEmbedDialog extends ControllerBase { - public function form() { - $entity = Media::create(['bundle' => 'file', 'uid' => 1]); + public function form($editor, $media_type) { + $entity = Media::create(['bundle' => $media_type, 'uid' => $this->currentUser()->id()]); $form = $this->entityFormBuilder()->getForm($entity, 'editor_embed'); return $form; } + } From 81319c296930adc1283d1dbac649990d8dedbe57 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 15:32:17 -0500 Subject: [PATCH 12/15] Add missing method from entityembed trait. --- .../src/Plugin/Filter/MediaEmbedFilter.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php index 99dc13d8563d..41e3715c185a 100644 --- a/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php +++ b/core/modules/media/src/Plugin/Filter/MediaEmbedFilter.php @@ -245,6 +245,35 @@ protected function replaceNodeContent(\DOMNode &$node, $content) { $node->parentNode->removeChild($node); } + /** + * Convert the attributes on a DOMNode object to an array. + * + * This will also un-serialize any attribute values stored as JSON. + * + * @param \DOMNode $node + * A DOMNode object. + * + * @return array + * The attributes as an associative array, keyed by the attribute names. + */ + public function getNodeAttributesAsArray(\DOMNode $node) { + $return = []; + + // Convert the data attributes to the context array. + foreach ($node->attributes as $attribute) { + $key = $attribute->nodeName; + $return[$key] = $attribute->nodeValue; + + // Check for JSON-encoded attributes. + $data = json_decode($return[$key], TRUE, 10); + if ($data !== NULL && json_last_error() === JSON_ERROR_NONE) { + $return[$key] = $data; + } + } + + return $return; + } + /** * {@inheritdoc} */ From 99cc57a1c29c80eb4ac4f16ef34bbfa9ec3414b9 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sun, 7 Jan 2018 15:34:10 -0500 Subject: [PATCH 13/15] Clean out unused entity embed cruft. --- .../modules/media/src/Form/MediaFormEmbed.php | 41 +++++-------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/core/modules/media/src/Form/MediaFormEmbed.php b/core/modules/media/src/Form/MediaFormEmbed.php index 856447ad9284..89b471d08d5c 100644 --- a/core/modules/media/src/Form/MediaFormEmbed.php +++ b/core/modules/media/src/Form/MediaFormEmbed.php @@ -12,6 +12,11 @@ use Drupal\editor\Ajax\EditorDialogSave; use Drupal\media\MediaForm; +/** + * Form controller for the media embed add/edit forms. + * + * @internal + */ class MediaFormEmbed extends MediaForm { /** @@ -19,11 +24,12 @@ class MediaFormEmbed extends MediaForm { */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + $form['#tree'] = TRUE; $form['#attached']['library'][] = 'editor/drupal.editor.dialog'; $form['#ajax'] = [ 'callback' => '::ajaxFormRebuild', ]; - $form['#prefix'] = '
'; + $form['#prefix'] = '
'; $form['#suffix'] = '
'; return $form; } @@ -42,31 +48,23 @@ public function ajaxFormRebuild($form, FormStateInterface $form_state) { '#type' => 'status_messages', '#weight' => -10, ); - $response->addCommand(new HtmlCommand('#entity-embed-dialog-form', $form)); + $response->addCommand(new HtmlCommand('#media-embed-dialog-form', $form)); } else { - // Serialize entity embed settings to JSON string. - + // Embed the entity element in the editor and close the dialog. $values = $form_state->getValues(); - $values['attributes'] = [ 'data-embed-button' => 'media', 'data-entity-embed-display' => 'view_mode:media.full', 'data-entity-type' => 'media', 'data-entity-uuid' => $this->getEntity()->uuid(), ]; - if (!empty($values['attributes']['data-entity-embed-display-settings'])) { - $values['attributes']['data-entity-embed-display-settings'] = Json::encode($values['attributes']['data-entity-embed-display-settings']); - } // Filter out empty attributes. $values['attributes'] = array_filter($values['attributes'], function($value) { return (bool) Unicode::strlen((string) $value); }); - // Allow other modules to alter the values before getting submitted to the WYSIWYG. - $this->moduleHandler->alter('entity_embed_values', $values, $entity, $display, $form_state); - $response->addCommand(new EditorDialogSave($values)); $response->addCommand(new CloseModalDialogCommand()); } @@ -74,28 +72,9 @@ public function ajaxFormRebuild($form, FormStateInterface $form_state) { return $response; } - /** + /** * {@inheritdoc} */ - public function save(array $form, FormStateInterface $form_state) { - $saved = parent::save($form, $form_state); - $context = ['@type' => $this->entity->bundle(), '%label' => $this->entity->label()]; - $logger = $this->logger('media'); - $t_args = ['@type' => $this->entity->bundle->entity->label(), '%label' => $this->entity->label()]; - - if ($saved === SAVED_NEW) { - $logger->notice('@type: added %label.', $context); - drupal_set_message($this->t('@type %label has been created.', $t_args)); - } - else { - $logger->notice('@type: updated %label.', $context); - drupal_set_message($this->t('@type %label has been updated.', $t_args)); - } - - // $form_state->setRedirectUrl($this->entity->toUrl('canonical')); - return $saved; - } - public function actionsElement(array $form, FormStateInterface $form_state) { $element = parent::actionsElement($form, $form_state); $element['submit']['#ajax'] = [ From 5c67bcd862702e619d27e79c0b72b549d5c6830f Mon Sep 17 00:00:00 2001 From: charginghawk Date: Sun, 7 Jan 2018 15:52:21 -0500 Subject: [PATCH 14/15] Restrict test to img tag. --- .../tests/src/Kernel/MediaEmbedFilterTest.php | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php index c87c884bb99e..a3c5d2779cb9 100644 --- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -4,6 +4,7 @@ use Drupal\filter\FilterPluginCollection; use Drupal\media\Entity\Media; +use Drupal\Component\Utility\Html; /** * Tests the media content input filter. @@ -43,7 +44,13 @@ public function testMediaEmbedFilter() { // Create test function. $test = function ($input) use ($filter) { - return $filter->process($input, 'und'); + // Run $input through $filter. + $filtered_media = $filter->process($input, 'und')->getProcessedText(); + // Extract tag. + $dom = Html::load($filtered_media); + $img = $dom->getElementsByTagName('img')[0]; + $filtered_media_img = $dom->saveHTML($img); + return $filtered_media_img; }; // Create media entity. @@ -59,15 +66,20 @@ public function testMediaEmbedFilter() { $renderer = \Drupal::service('renderer'); $rendered_media = $renderer->renderPlain($build)->__toString(); + // Extract tag. + $dom = Html::load($rendered_media); + $img = $dom->getElementsByTagName('img')[0]; + $rendered_media_img = $dom->saveHTML($img); + // Test filter using data-entity-embed-display attribute. $input = ''; - $expected = $rendered_media; - $this->assertSame($expected, $test($input)->getProcessedText()); + $expected = $rendered_media_img; + $this->assertSame($expected, $test($input)); // Test filter using data-view-mode attribute. $input = ''; - $expected = $rendered_media; - $this->assertSame($expected, $test($input)->getProcessedText()); + $expected = $rendered_media_img; + $this->assertSame($expected, $test($input)); } } From 604c5c0d14701812df90a63141f7e834148abbc1 Mon Sep 17 00:00:00 2001 From: Kelly Lucas Date: Sat, 27 Jan 2018 09:44:38 -0500 Subject: [PATCH 15/15] Add WIP editor id. --- core/modules/media/js/plugins/mediaembed/plugin.js | 1 + core/modules/media/src/Form/MediaFormEmbed.php | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/core/modules/media/js/plugins/mediaembed/plugin.js b/core/modules/media/js/plugins/mediaembed/plugin.js index ac72b94aab2a..9474d1896273 100644 --- a/core/modules/media/js/plugins/mediaembed/plugin.js +++ b/core/modules/media/js/plugins/mediaembed/plugin.js @@ -39,6 +39,7 @@ var existingElement = getSelectedEmbeddedEntity(editor); var existingValues = {}; + existingValues['editor-id'] = editor.element.getId(); if (existingElement && existingElement.$ && existingElement.$.firstChild) { var embedDOMElement = existingElement.$.firstChild; // Populate array with the entity's current attributes. diff --git a/core/modules/media/src/Form/MediaFormEmbed.php b/core/modules/media/src/Form/MediaFormEmbed.php index 89b471d08d5c..71f096a19f79 100644 --- a/core/modules/media/src/Form/MediaFormEmbed.php +++ b/core/modules/media/src/Form/MediaFormEmbed.php @@ -31,6 +31,18 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['#prefix'] = '
'; $form['#suffix'] = '
'; + $input = $form_state->getUserInput(); + $editor_dom_id = ''; + if (!empty($input['editor_object'])) { + $editor_dom_id = $input['editor_object']['editor-id']; + } + elseif (!empty($input['editor_dom_id'])) { + $editor_dom_id = $input['editor_id']; + } + $form['editor_dom_id'] = [ + '#type' => 'hidden', + '#value' => $editor_dom_id, + ]; return $form; }