Problem/Motivation

1) The caption filter currently always wraps the captioned element in a <figure> block to which it adds its own <figcaption>. This creates unnessecarily nested markup in case the element itself is already presented as/contains a <figure> with a <figcaption>.

2) Additionally, the $node->C14N() call strips all comments, which includes Twig debug comments (we can deal with that) and compatibility comments like those used for responsive images: <!--[if IE 9]><video style="display: none;"><![endif]--> (this is bad and will cause problems).

Example setup for 1) using entity_embed:

  1. Media type "Image" with a template like <figure>{{ content.field_image }}<figcaption>{{ name }}</figcaption></figure>
  2. Some content in which the media entity is embedded with a specified custom caption

Expected result:
<figure>{{ content.field_image }}<figcaption>{{ my overridden caption}}</figcaption></figure>

Actual result (using default filter-caption.html.twig):

<figure>
  <figure>
    {{ content.field_image }}
    <figcaption>{{ name }}</figcaption>
  </figure>
  <figcaption>{{ my overridden caption }}</figcaption>
</figure>

Proposed resolution

1) Re-use an already present <figcaption> element.
2) Keep comments when building $altered_html.

Remaining tasks

Discuss what we need/want to do. Review.

Issue fork drupal-2935625

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

ckaotik created an issue. See original summary.

ckaotik’s picture

Assigned: ckaotik » Unassigned
Status: Active » Needs review
FileSize
2.79 KB

I've written a patch that addresses both issues, but we should nontheless discuss if we need/want to address both parts.

The patch will use the first <figcaption> element found in the captioned element - for nested <figure>s, this should be the group description, and if there's multiple captions in a figure we have invalid markup anyways.

For the comments, I've changed the $node->C14N() call to $node->C14N(FALSE, TRUE) which solves that nicely.

ckaotik’s picture

Issue summary: View changes

Updated issue summary.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jonraedeke’s picture

Thank you for this patch, it works very well. This is a common use case.

jonraedeke’s picture

One thing I noticed with this patch is that you see both the caption from the field and the overridden caption in the text editing area.

In D7, it was possible to override fields on a file entity when embedding. This was ideal, because you could see the value of the caption field from the media entity before overriding.

jonraedeke’s picture

Taking another look, my caption field was not themed in the wysiwyg to output as figure and figcaption, so that is why it is not getting replaced. My other comment about pre-populating caption field data probably belongs in the Entity Embed module.

This patch looks good to me.

jonraedeke’s picture

This filter should also check for the <figure> wrapper. An image themed like this with no caption:

<figure>{{ content.field_image }}</figure>

still gets double wrapped with the <figure> tag.

<figure>
  <figure>
    {{ content.field_image }}
  </figure>
  <figcaption>{{ my overridden caption }}</figcaption>
</figure>
ckaotik’s picture

Good point, so basically add a fallback check if there's a <figure> element but no included <figcaption>, place it last in the figure wrapper?

jonraedeke’s picture

Exactly, that would be great.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

szeidler’s picture

This would be an excellent feature. I tried to utilize it for the Media embed option for CKEditor in Drupal 8.8. Unfortunately it fails by the fact, that "Caption images" filter is forced to run before "Embed media". This makes the caption filter not knowing if there will be a <figcaption> in the rendered media.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

jonraedeke’s picture

Project: Drupal core » Entity Embed
Version: 9.1.x-dev » 8.x-1.x-dev
Component: filter.module » Code
Status: Needs review » Active
Related issues: +#3052239: [media entities] Prepopulate caption with Media field data

Good point szeidler, the order of the filters appears to be forced in https://www.drupal.org/project/entity_embed/issues/2752253.

This is really only an issue when using entity embed, so I think it might be better discussed there first.

It's too bad the caption filter has to can't run last. I was excited about this prospect as it would potentially let me replace existing rendered figcaptions displayed in the image media entity itself. That would be an easy way to override captions from the media object, like used to be possible in D7. Also, it would allow for printing fields after <figcaption>, like Image Credit.

This issue also prevents theming image media as without a caption since the filter doesn't run. So the markup for images changes depending on whether there is a caption or not, which is not ideal. The core caption filter really assumes that the contents of the item being captioned is a simple img element, not a fully fielded and rendered image media entity.

I wonder if entity embed needs it's own caption filter functionality to account for rendered entities, not just img tags.

jonraedeke’s picture

Project: Entity Embed » Drupal core
Version: 8.x-1.x-dev » 9.2.x-dev
Component: Code » filter.module

I was wrong that this is only relevant for the entity_embed module. Embedding media with core has this issue also. Moving back to core.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jonraedeke’s picture

Unfortunately, this is still an issue with CKEditor 5 implementation in core.

I'm just digging into CKEditor 5 plugin coding, but it appears that CKEditor 5 really isn't aware of the media markup.

jayhuskins made their first commit to this issue’s fork.

jayhuskins’s picture

For anyone interested, here's how I solved this for my website:

  • Do not display a figure around the media in the media display
  • Create a custom text filter that goes between "Caption images" and "Embed media" which adds a figure if there isn't already one

Here is the code for the process function in my custom text filter:

  public function process($text, $langcode) {
    $result = new FilterProcessResult($text);

    if (stristr($text, '<drupal-media') === FALSE) {
      return $result;
    }

    $dom = Html::load($text);
    $xpath = new \DOMXPath($dom);
    $query = '//drupal-media[@data-entity-type="media" and normalize-space(@data-entity-uuid)!=""]';

    foreach ($xpath->query($query) as $node) {
      $parent = $node->parentNode;
      if ($parent->tagName === 'a') {
        $parent = $parent->parentNode();
      }
      if ($parent->tagName === 'figure') {
        continue;
      }

      // Move classes from node to figure.
      $classes = $node->getAttribute('class');
      $node->removeAttribute('class');
      $figure = $dom->createElement('figure');
      $figure->setAttribute('class', $classes);

      // Place figure between parent and node.
      $parent->replaceChild($figure, $node);
      $figure->appendChild($node);
    }

    $result->setProcessedText(Html::serialize($dom));
    return $result;
  }
pawel.traczynski’s picture

The Drupal core caption filter did not work for me either for the resons mentioned above.

Here is how I achived captions for embedded media images while being in full control of the HTML markup.

I needed to give the user an option to:
- embed image while being able to pick image style: I have created one style for tall/portrait image and one for wide/full content width image
- allow them to provide caption
- use my own markup for the image printed with image style and the caption

1. I have setup media bundle 'image' and added 'media_caption' formatted text field in it. The caption format allowed paragraphs and links so that instead of plain text captions, a caption could be multiple paragraphs or could contain a link. For this purpose I have used a text format that I already had on my site that allows exactly and only this.

2. Then configured two different view modes for media 'image', where each view mode used different image style for the media_image field. One view mode machine name was 'image_full' and the other was 'image_tall'.

3. The custom templates for embedded image media I needed to be global, that is I neeeded them to be picked up independently of theme being in use. For this reason I have registered my own media templates in my module:

/**
 * Implements hook_theme().
 */
function MODULE_theme($existing, $type, $theme, $path) {
  return [
    // Use custom templates for 'media' with 'image' bundle and
    // with 'image-...' view modes.
    'media__image__image_full' => [
      'template' => 'media-image',
      'render element' => 'elements',
      'base hook' => 'media',
    ],
    'media__image__image_tall' => [
      'template' => 'media-image',
      'render element' => 'elements',
      'base hook' => 'media',
    ],
  ];
}

As you can see I used single template for both view modes. Because of using my own templates only for my view modes, other view modes, like those used in the Media administration pages are not impacted and will continue to display normally without breaking the admin UI styling.

4. The custom media-image.html.twig template looked like this:

<figure{{ attributes }}>
  {{ title_suffix.contextual_links }}
  {{ content|without('media_caption') }}

  {% if media_caption %}
    <figcaption class="fix-margins">{{ media_caption }}</figcaption>
  {% endif %}
</figure>

5. Then to support my own template and my markup requirements I added this template preprocess:

/**
 * Preprocess variables for media template.
 *
 * Providing custom classes and values to media-image templates.
 */
function MODULE_preprocess_media(&$variables) {
  /** @var \Drupal\media\MediaInterface $media */
  $media = $variables['elements']['#media'];

  /** @var \Drupal\MODULE\Common $common */
  $common = \Drupal::service('MODULE.common');

  $bundle = $media->bundle();
  $view_mode = $variables['elements']['#view_mode'];

  if ($bundle == 'image' && in_array($view_mode, ['image_full', 'image_tall'])) {
    // Converts view mode like 'image_full' to 'full'.
    $image_mode = str_replace('image_', '', $view_mode);

    $variables['attributes']['class'][] = 'img';
    $variables['attributes']['class'][] = 'img-' . $image_mode;

    // This just assigns caption value from media entity to a variable.
    // If value is missing this will equal FALSE.
    $variables['media_caption'] = $common->fieldValue('media_caption', $media, FALSE);
  }
}

Thats it. Now when Embed Media button it pressed, when user uploads a new image, they will see a custom caption field. In addition when they insert the image into CKEditor5, they will see a dropdown to pick the image style to use.

I have implemented this as an "Insert" module replacement for CK5.

So far the only downsides of this solution that I see are:
- once the image has been inserted into ckeditor5 content, then if user wants to edit the caption again, they have to edit it in the media, for example by editing the media on the media administration page
- the caption is global for a single media item. You cannot insert the same media media in different nodes while having different captions for each