Upgrading Entity Embed to its 1.0 release has caused some minor bugs on my Drupal instances. Here are the ones that I ran into and the workarounds I used to fix them.

Quick Edit Javascript errors

If a user with the required entity edit and quick edit permissions visits an entity that does have embedded entities in its formatted text field, then Quick Edit will throw errors in the JS console:

drupal.js?v=8.7.3:13 Uncaught Error: Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="media/29/field_media_image/en/full"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="media/29"]. This is typically caused by the theme's template for this entity type forgetting to print the attributes.

This happens because the EntityReferenceFieldFormatter::disableQuickEdit method removes the data-quickedit-entity-id attribute of the embedded entity’s fields.

My workaround was quite simple and short (I used two hooks):

  1. I simply implemented the hook_entity_view_alter() hook to check if the method above is added to $build['#pre_render']. If so, I loop over the fields of the build and flag them with my own, private boolean:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  /**
   * Implements hook_entity_view_alter().
   *
   * Entity Embed only removes the main data-quickedit-entity-id attribute, but
   * leaves the fields' data-quickedit-field-id attributes on the fields.
   * That leads to JS errors - and we want to avoid them.
   */
  function st_media_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
    if (
      !empty($build['#pre_render']) &&
      in_array('Drupal\entity_embed\Plugin\entity_embed\EntityEmbedDisplay\ViewModeFieldFormatter::disableQuickEdit', $build['#pre_render'])
    ) {
      $field_keys = Element::children($build);
  
      foreach ($field_keys as $field_key) {
        $build[$field_key]['#remove_quickedit_attribute'] = TRUE;
      }
    }
  }

Unfortunately we cannot do more here because the quick edit attributes of the output are added by the quickedit modules’ field preprocess implementation, so we don’t have them at the time when this hook is invoked.

  1. In a hook_preprocess_field() implementation I check if the flag exists. If it does, then I remove the related HTML attributes from the field’s renderable array:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     /**
      * Implements hook_preprocess_field().
      */
     function st_media_preprocess_field(&$variables) {
       if (!empty($variables['element']['#remove_quickedit_attribute'])) {
         // Remove quickedit attribute from fields of entities that are preprocessed
         // by entity_embed.
         unset($variables['attributes']['data-quickedit-field-id']);
       }
     }
    

If you are lucky like me, and your module name follows both entity_embed and quickedit in the alphabet, these hooks will fix the errors. If you aren’t that lucky, then you should move your hook implementations after the related ones:

  • Your entity view alter hook should follow entity_embed’s implementation.
  • Your field preprocessor hook should follow quickedit’s implementation.

Unloaded Blazy images in CKEditor

Since I usually use Blazy for formatting my images over the sites I build, and because of that CKEditor does not adds scripts to its editor’s <iframe>, I have to change the disarmed data-src (used on <img>) and data-srcset (used on <img> and <source>) attributes back to src and srcset: this is the easiest way to make these responsive images displayed in the editor.

Before the 1.0 release (so even in both rc releases) Entity Embed used the Embed module’s preview route for rendering and displaying the embed entities inside CKEditor. I had a workaround for this route, so I was a bit surprised why the images were broken again. Well, because beginning from 1.0, Entity Embed uses its own preview route!

To solve this issue I simply changed the controller of the preview route to a custom one that makes the required changes on the original output:

I have a route subscriber (st_media.services.yml):

1
2
3
4
services:
  st_media.route_subscriber:
    class: Drupal\st_media\Routing\RouteSubscriber
    tags: [{ name: event_subscriber }]

In the route subscriber, I change the controller of the page to a new one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace Drupal\st_media\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
    // Fix unloaded blazy images in editor.
    if ($route = $collection->get('entity_embed.preview')) {
      // Workaround for entity embed preview (after entity_embed 1.0).
      if ($route->getDefault('_controller') === '\Drupal\entity_embed\Controller\PreviewController::preview') {
        $route->setDefault('_controller', '\Drupal\st_media\Controller\EntityEmbedPreviewController::preview');
      }
    }
  }

}

The new controller extends the original one. I call the original method of the parent class to get the “buggy” response, process and fix the response content, and return the fixed markup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace Drupal\st_media\Controller;

use Drupal\Component\Utility\Html;
use Drupal\entity_embed\Controller\PreviewController;
use Drupal\filter\FilterFormatInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Replacement controller for entity embed preview.
 *
 * Restores blazy-processed images for entity embed preview route.
 */
class EntityEmbedPreviewController extends PreviewController {

  /**
   * {@inheritdoc}
   */
  public function preview(Request $request, FilterFormatInterface $filter_format) {
    // This will throw an exception in the same cases when the original
    // controller did.
    $response = parent::preview($request, $filter_format);
    $html_dom = Html::load($response->getContent());
    $images = $html_dom->getElementsByTagName('img');
    $sources = $html_dom->getElementsByTagName('source');

    foreach ($images as $image) {
      foreach (['srcset', 'src'] as $attribute_name) {
        $data_attibute = $image->getAttribute('data-' . $attribute_name);
        if (!empty($data_attibute)) {
          $image->setAttribute($attribute_name, $data_attibute);
          $image->removeAttribute('data-' . $attribute_name);
        }
      }
    }

    foreach ($sources as $source) {
      $data_attibute = $source->getAttribute('data-srcset');
      if (!empty($data_attibute)) {
        $source->setAttribute('srcset', $data_attibute);
        $source->removeAttribute('data-srcset');
      }
    }

    return $response->setContent(Html::serialize($html_dom));
  }

}

Filter format settings validation issues

The stable Entity Embed added a validation for the filter format settings form that checks whether the required attributes are set up properly in the filter_html settings. Sadly, the validation is superficial (I mean it is too strict): it does not believe that the data-* attribute pattern allows all of its required attributes that are prefixed with data-.

Here I implemented the same form_id alter hooks as entity_embed did for the filter form, and replaced its validation callback with a new one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Helper callback for our entity embed workaround.
 *
 * Replaces entity_embed's form validation with an another one that is a little
 * bit smarter than the original.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 */
function _st_media_entity_embed_validate_switcher(array &$form) {
  if (
    !empty($form['#validate']) &&
    ($entity_embed_validate_key = array_search('entity_embed_filter_format_edit_form_validate', $form['#validate'])) !== FALSE
  ) {
    $form['#validate'][$entity_embed_validate_key] = '_st_media_filter_format_edit_form_validate';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function st_media_form_filter_format_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  _st_media_entity_embed_validate_switcher($form);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function st_media_form_filter_format_add_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  _st_media_entity_embed_validate_switcher($form);
}

And then, I copied the whole body of the original validation function to my replacement callback _st_media_filter_format_edit_form_validate(), and right before this condition I added the following lines:

1
2
3
4
5
6
7
8
9
$allowed_attributes =  array_keys($allowed['drupal-entity']);

if (
  in_array('data-*', $allowed_attributes) &&
  in_array('alt', $allowed_attributes) &&
  in_array('title', $allowed_attributes)
) {
  $missing_attributes = NULL;
}

Note that this last workaround is only a hotfix; it only accepts the configuration that I used to use in my filter configurations. The right solution must be developed following the patterns of the FilterHtml.php class.