Problem/Motivation

This issue is split off from #2801307: [META] Support WYSIWYG embedding of media entities.

The first part of WYSIWYG integration for Media is the ability to display a media entity by embedding it in the textual content of another entity -- i.e., a media item displayed inline in the body of a node. To do this, we will need a text filter which can render media entities referenced by some sort of embed code, similar to the way the contrib Entity Embed module works.

Proposed resolution

Create a @Filter plugin which can render a media entity from an embed code, using a custom tag named <drupal-media> which supports data-entity-type, data-entity-uuid and data-view-mode attributes.

(See #25 for background.)

Remaining tasks

  1. Import the code from #2801307: [META] Support WYSIWYG embedding of media entities, iterate on it, review it until it is nice, and commit it.
  2. Stabilize https://www.drupal.org/project/entity_embed, port over all test coverage, to ensure a maximally stable new filter in core.
  3. Reviews!

User interface changes

None.

API changes

A new text filter will be added, which will support machine-readable Drupal embed codes.

Data model changes

No

Testing steps

  • Install latest 8.8.x
  • Apply the patch
  • Go to Administration => Configuration => Content authoring => Text formats and editors
  • Select a text format (e.g. Basic HTML)
  • Check " Embed media" under "Enabled filters"
  • Add <drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption> in "Limit allowed HTML tags and correct faulty HTML" (data-view-mode is optional as of #97) if the filter is enabled and "Save configuration".

    Note that this is also visible there, but for the purpose of testing this functionality, we don't need to change the default settings:

  • Go to node add article (/node/add/article)
  • Select "Source" from the CKEditor
  • Add
    <drupal-media data-align="center" data-caption="The first core Media embed ever." data-entity-type="media" data-entity-uuid="84911dc4-c086-4781-afc3-eb49b7380ff5"></drupal-media>
    
    <p>and</p>
    
    <drupal-media data-align="center" data-caption="This media entity is missing" data-entity-type="media" data-entity-uuid="wrong"></drupal-media>
    

    and save, you should now see:

CommentFileSizeAuthor
#104 interdiff.txt7.39 KBWim Leers
#104 2940029-104.patch68.43 KBWim Leers
#99 2940029-99.patch68.11 KBWim Leers
#99 interdiff.txt3.08 KBWim Leers
#97 2940029-97.patch67.94 KBWim Leers
#97 interdiff.txt13.91 KBWim Leers
#97 media_embed filter settings.png44.44 KBWim Leers
#91 2940029-91.patch66.53 KBWim Leers
#91 interdiff.txt3.39 KBWim Leers
#90 Screenshot 2019-07-04 at 15.01.40.png77.96 KBWim Leers
#86 2940029-86.patch66.59 KBWim Leers
#86 interdiff.txt3.82 KBWim Leers
#85 2940029-85.patch65.82 KBWim Leers
#85 interdiff.txt9.98 KBWim Leers
#84 2940029-84.patch60.64 KBWim Leers
#84 interdiff.txt9.08 KBWim Leers
#81 2940029-81.patch60.6 KBWim Leers
#81 interdiff.txt10.08 KBWim Leers
#68 2940029-68.patch60.59 KBWim Leers
#68 interdiff.txt5.9 KBWim Leers
#65 2940029-65.patch59.77 KBWim Leers
#65 interdiff.txt11.43 KBWim Leers
#64 interdiff.txt5.84 KBWim Leers
#64 2940029-64.patch48.39 KBWim Leers
#4 2940029-input-filter-embed.patch17.17 KBkrlucas
#8 2940029-8.patch13.38 KBnathandentzau
#11 2940029-11.patch17.98 KBkrlucas
#11 interdiff-2940029-8-11.txt5.33 KBkrlucas
#17 2940029-17.patch17.96 KBlegovaer
#18 2940029-18.patch19.31 KBlegovaer
#18 interdiff-17-18.txt5.36 KBlegovaer
#20 2940029-diff-18-20.txt1.97 KBvijaycs85
#20 2940029-20.patch19.38 KBvijaycs85
#20 2940029-20.png1.4 MBvijaycs85
#27 Screen Shot 2018-12-06 at 16.58.59.png221.05 KBWim Leers
#27 2940029-27.patch14.58 KBWim Leers
#29 interdiff.txt1.04 KBWim Leers
#29 2940029-29.patch15.02 KBWim Leers
#31 interdiff.txt5.95 KBWim Leers
#31 2940029-31.patch14.85 KBWim Leers
#32 interdiff.txt945 bytesWim Leers
#32 2940029-32.patch14.66 KBWim Leers
#33 interdiff.txt4.74 KBWim Leers
#33 2940029-33.patch15.44 KBWim Leers
#38 interdiff.txt4.21 KBWim Leers
#38 2940029-38.patch15.39 KBWim Leers
#43 Screenshot 2019-06-25 at 16.56.01.png222.22 KBWim Leers
#43 interdiff.txt1.85 KBWim Leers
#43 2940029-43.patch16.55 KBWim Leers
#45 interdiff.txt1.06 KBWim Leers
#45 2940029-45.patch17.57 KBWim Leers
#46 interdiff.txt25.09 KBWim Leers
#46 2940029-46.patch41.29 KBWim Leers
#47 interdiff.txt3.39 KBWim Leers
#47 2940029-47.patch42.85 KBWim Leers
#48 interdiff.txt6.16 KBWim Leers
#48 2940029-48.patch46.6 KBWim Leers
#49 interdiff.txt4.13 KBWim Leers
#49 2940029-49.patch50.08 KBWim Leers
#50 interdiff.txt4.46 KBWim Leers
#50 2940029-50.patch52.28 KBWim Leers
#51 media_embed testing steps screenshot.png737.69 KBWim Leers
#57 interdiff.txt649 bytesWim Leers
#57 2940029-57.patch52.25 KBWim Leers
#58 interdiff.txt808 bytesWim Leers
#58 2940029-58.patch52.24 KBWim Leers
#59 interdiff.txt4.39 KBWim Leers
#59 2940029-59.patch47.9 KBWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

phenaproxima created an issue. See original summary.

phenaproxima’s picture

krlucas’s picture

Assigned: Unassigned » krlucas
krlucas’s picture

Here's the input filter only patch.

krlucas’s picture

Status: Active » Needs review
phenaproxima’s picture

Status: Needs review » Needs work

Thank you, @krlucas! This is an excellent start.

The first major change I would request here is that we move the filter into the Filter module directly, and remove all dependencies or mentions of Media. This thing should work for any entity type, with or without the Media module.

nathandentzau’s picture

Assigned: krlucas » nathandentzau
nathandentzau’s picture

FileSize
13.38 KB

Hey phenaproxima,

I've generalized krlucas's patch and moved it to the filter module. I've gone ahead and refactored the filter class to dry up some of the logic. I ran into an issue however when working on this. If you're embedding an entity that references itself and we get a recursive nesting fatal error. I'm not 100% sure how we should handle this. I added a @todo for this in code.

Lastly I ran out of time today to work on tests, however I can knock those out another day.

krlucas’s picture

@nathandentzau thanks! I'll give it a test drive.

Note the test in #4 was dropped in #8. We should add it back even if it's not quite right.

phenaproxima’s picture

Issue tags: +Needs tests

I've been discussing the recursive rendering stuff with my colleagues at Global Sprint Weekend.

The filter generally is quite straightforward and looks good, but we really need to handle the recursive rendering case. I think we need an explicit test of that case before we proceed any further.

krlucas’s picture

Status: Needs work » Needs review
FileSize
17.98 KB
5.33 KB

Updated patch with test both for the basic functionality of the filter but also with a test for recursion.

I had to reduce the recursion depth limit in the filter from 20 to 2 to get the recursive exception to be thrown before phpunit got weird.

The test definitely needs to be cleaned up.

Status: Needs review » Needs work

The last submitted patch, 11: 2940029-11.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

krlucas’s picture

It appears test bot is seeing whitespace differences in the output rendered directly from the entity view builder and that output from the embed filter. We could probably fix by stripping all whitespace from both expected and actual though I'm not sure if that's a fair test.

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.

phenaproxima’s picture

phenaproxima’s picture

legovaer’s picture

The patch in #11 did no longer apply to the latest commit on 8.7.x. Re-patched it in order to continue working on this issue.

legovaer’s picture

Status: Needs work » Needs review
FileSize
19.31 KB
5.36 KB
vijaycs85’s picture

Issue summary: View changes

Tested manually and here are some findings:

  1. +++ b/core/modules/filter/src/Plugin/Filter/FilterEntityEmbed.php
    @@ -0,0 +1,323 @@
    +            sprintf('Unable to load embedded %s entity %s.', $entity_type, $id)
    

    $id is undefined.

  2. +++ b/core/modules/filter/src/Plugin/Filter/FilterEntityEmbed.php
    @@ -0,0 +1,323 @@
    +        <drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" />');
    

    there is no data-view-mode, it's data-entity-embed-display now?

  3. Image in the embed entity is not getting displayed properly.

1. Fixed #1 and #2
2. Updated issue summary with testing steps.

vijaycs85’s picture

FileSize
1.97 KB
19.38 KB
1.4 MB
vijaycs85’s picture

Issue tags: +DistributedSprintUK18
Berdir’s picture

+++ b/core/themes/stable/templates/content-edit/entity-embed.html.twig
@@ -0,0 +1,15 @@
+<article{{ attributes }}>{{ children }}</article>

I'm not so sure that makes sense, as usually the entity itself will also be an article, so you get <article><article></article></article>?

The documentation says this is a container/wrapper, so that sounds like a div to me?

I also think there are older, existing issues for this and we will need a plan on how to handle this in the entity_embed module, as this is going to clash with that. Based on the very similar code, I guess this implementation is very much based on that (which makes me wonder a bit how copyright/credit rules apply when contrib code is moved to core, we should probably at least credit the people who worked on the contrib module?) but that had its own plugin system that could be extended that is gone but there still seem to be left-overs of that in at least one place (data-entity-embed-display defaults).

It's entity_embed vs filter, so I think the result would be that filter.module's plugin would win (because being later alphabetically and then existing data might not work anymore, e.g. cases that used something different than the default entity reference display plugin).

Wim Leers’s picture

I also think there are older, existing issues for this and we will need a plan on how to handle this in the entity_embed module

I think that'd be #2927867: Why is my embedded image being wrapped in an <article> tag?.

Wim Leers’s picture

Assigned: nathandentzau » Wim Leers

I have yet to form a complete analysis and opinion about the patch attached to this issue. I've started triaging the entire https://www.drupal.org/project/issues/entity_embed issue queue to ensure we don't introduce knowable bugs into core.

Concerns about the current patch:

  1. Comparing the \Drupal\filter\Plugin\Filter\FilterEntityEmbed class that this patch introduces with the upstream \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter whose functionality it is bringing into core, I'm surprised by the amount of change:
    1 file changed, 230 insertions(+), 97 deletions(-)
    

    That's pretty much a rewrite. That's relatively scary to be honest.

    It's absolutely possible that there are good reasons that justify this, but I don't see them listed here. Ideally, any concerns we have with the upstream code should be fixed there first.

  2. +++ b/core/modules/filter/src/Plugin/Filter/FilterEntityEmbed.php
    @@ -0,0 +1,324 @@
    +    // The caption text is double-encoded, so decode it here.
    +    if (isset($context['data-caption'])) {
    +      $context['data-caption'] = Html::decodeEntities($context['data-caption']);
    ...
    +    // Maintain data-caption if it is there.
    +    if (isset($context['data-caption'])) {
    +      $build['#attributes']['data-caption'] = $context['data-caption'];
    +    }
    

    This … seems sketchy. It also violates the principle that Drupal text @Filter plugins should be composable and not need to be aware of each other. This code is specifically aware of the data-caption attribute, which is handled by the \Drupal\filter\Plugin\Filter\FilterCaption filter in core.

    Note that the original code for this filter, at \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter, is NOT doing anything with data-caption. So this is at least one place where my initial gut feeling in the previous point seems to be confirmed.

  3. My comment at #2577891-3: Entity Embed 8.x-1.0.0-rc1 release from a month before Drupal 8.0.0 was released:

    Given #2593315: Improve documentation of hook_entity_embed_alter() and #2593379: Analyze/improve/fix handling of bubbleable metadata in entity_embed, I think we need to first seriously scrutinize the APIs of Entity Embed, to prevent API changes after RC?

    Unfortunately, there's no progress to report on there. That does not inspire confidence in the state of the Entity Embed module's code, especially when also considering its last commit dates back over 18 months, and the one before that over two years.

    (That's NOT to be held against its maintainers — maintaining open source code requires a lot of work! But it does mean there is a certain level of risk to bring this code into Drupal core. That's my key concern here.)

I'll dig deeper and will report back by the end of the day tomorrow.

phenaproxima’s picture

Issue tags: +BarcelonaMediaSprint

Discussed this in Barcelona with @Wim Leers, @alexpott, @marcoscano, @chr.fritsch, and @seanB. We were able to arrive pretty quickly at a consensus of what we want to build here, and what we want to build on top of it.

The first thing we are going to do is build an input filter. This filter will do essentially the same thing as the one in the Entity Embed contrib module, with a few differences:

  • The filter will be written to work with media entities, and is intended squarely for embedding media items in formatted text. If people want to embed non-media entities, they'll need to use Entity Embed.
  • It will not support Entity Embed's concept of "display plugins". The filter will accept a data-view-mode attribute, and use the specified view mode to render the embedded entity. Display plugins will continue to be supported in Entity Embed, for more advanced use cases that core will not support.
  • At @alexpott's suggestion, the filter will use drupal-media tags as placeholders for the embedded entities, rather than the drupal-entity tag used by Entity Embed. This greatly reduces technical risks because it means our embedding mechanism will not need to provide perfect compatibility with Entity Embed's, at least for now; this gives us the freedom to iterate without being shackled to Entity Embed and whatever it's doing. In other words, we're not setting the expectation that we are completely replacing Entity Embed. When the dust settles and things look ready to commit, we'll re-evaluate whether we feel the filter can interoperate with Entity Embed. If we think it can, we'll switch back to the drupal-entity tag and commit with that.
  • To simplify the "out of the box" use case, we will ship this filter with a new view mode for media entities, called "Embedded" (or similar). This is the view mode which will be used by default to render embedded entities. Site builders will be able to enable the use of additional view modes, which means that content editors will need to select which view mode to use when they embed a media item. If no additional view modes are enabled, the Embedded view mode will be used and no choice will be presented to content editors. This will reduce the amount of clicks and cognitive load needed to embed a media item.
  • We will support the data-align attribute, so as to be interoperable with the alignment filter already in core. However, we aren't sure this needs to be part of the MVP, if it proves technically challenging.
  • We want to support captioning (i.e., be interoperable with caption filter in core), but this presents potential technical challenges -- is there a default caption? If so, where does it come from? -- and therefore we're not sure it needs to be MVP either.
  • We need to figure out #2752253: Document correct filter_html_image_secure + filter_align + filter_caption + entity_embed filters order, write tests, and ensure correct by default, since compatibility with other filters is heavily influenced by filter order. This is also needed in order to be sure our filter continues to work alongside Entity Embed (see my previous point about not setting the expectation that people can replace Entity Embed with this new filter we're building).
AaronMcHale’s picture

Sounds like a good MVP plan to me! Like you said, get it working just with Media first, then look to see if it can be broadened. Personally I'm a fan of the concepts that Drupal embodies about not being a cookie cutter solution, so I'd be keen to see drupal-entity remain on the cards for future work, but I think for now keeping the scope narrow is sensible.

Looking forward to seeing how this progresses.

Wim Leers’s picture

We've made tons of progress on #2577891: Entity Embed 8.x-1.0.0-rc1 release in the past few days. Time to get a patch going for this issue!

This patch:

  • extracts a subset of \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter filter, to work only with media entities (see #25, first bullet) and view modes (see #25, second bullet)
  • uses <drupal-media> instead of <drupal-entity> (see #25, third bullet)
  • data-align and data-caption work (but #2752253-29: Document correct filter_html_image_secure + filter_align + filter_caption + entity_embed filters order, write tests, and ensure correct by default will still need to land upstream)
  • test coverage is super minimal for now, it's painful to write test coverage for now because Entity Embed's test coverage is doing rather rough HTML assertions, which are impossible to transform to Media entity HTML assertions because Media entities are rendered in more complex ways; we should first improve \Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest further
  • in-place editing and contextual links are explicitly disabled; the reason being that it's going to be a terrible UX if content authors can in-place edit embedded entities, because most of them will assume that will only affect that specific embed (to be ported to Entity Embed, and this will also affect its test coverage)

(#25, fourth bullet needs to be done in a follow-up, that's solely adding configuration.)

To test:

  1. install Standard + Media Library
  2. create an Image media item
  3. apply this patch, and enable its filter, add <drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption> to the allowed HTML tags
  4. there's no CKEditor integration yet (this is only a filter), so copy/paste this:
    <drupal-media data-align="center" data-caption="The first core Media embed ever." data-entity-type="media" data-entity-uuid="a1eec161-2f6e-42af-843e-703738a135eb" data-view-mode="embed"></drupal-media>
    
    <drupal-media data-align="center" data-caption="This media entity is missing" data-entity-type="media" data-entity-uuid="wrong" data-view-mode="embed"></drupal-media>
    
    <drupal-media data-entity-type="media" data-entity-uuid="" data-view-mode="embed"></drupal-media>
    

    and adjust the UUID to match the one of the media item you just created, and change the view mode to one that is available on your site

  5. You should see something like this:

    The first embed works as expected (but the caption filter's HTML needs to be hardened obviously), the second embed which is referring to a now-missing entity falls back nicely and the third embed whose UUID attribute is empty is ignored by the filter.

Status: Needs review » Needs work

The last submitted patch, 27: 2940029-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.04 KB
15.02 KB

This should be green.

A review of just the filter would be very welcome. Extreme nitpicking of that class is already welcome — this is going to be the heart of this feature, and it should be very reliable.

phenaproxima’s picture

Status: Needs review » Needs work

Excellent start! I love how clean and simple it is, and how much effort and thoughtfulness has been put into it.

  1. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    + *   description = @Translation("Embeds media items entities using a custom HTML tag. If used in conjunction with the 'Align/Caption' filters, make sure this filter is configured to run before them."),
    

    "Embeds media items entities"? That should be "Embeds media items". :) Also, didn't we discover that this filter should run _after_ align and caption, not before?

  2. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +class MediaEmbed extends FilterBase implements ContainerFactoryPluginInterface {
    

    We might want to mark the class @internal, no?

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +  /**
    +   * The entity type supported by this filter.
    +   *
    +   * @var string
    +   */
    +  const ENTITY_TYPE_ID = 'media';
    

    I don't know if this really adds much right now, but it's no big deal.

    Personally, I'd prefer using a derivative-style syntax, so that it becomes possible for embed filters to vary implementation by entity type (we could hard-code the plugin ID, for now, as embed:media, and then use $this->getDerivativeId() to refer to it -- this would have the same net effect as using a constant, but allow contrib to open this up in the future).

    Or, we could just plain use a deriver that hard-codes the available variants. For an example of this pattern, see OEmbedDeriver.

    Either way, this is not a thing we necessarily need to change now. Just thinking out loud.

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +   *   Language code in which the media entity must be rendered.
    

    s/must/should

  5. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +  private function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    

    Private method? I heartily approve!!

  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +    // - Contextual Links do not make sense for embedded entities; we only allow
    +    //   the host entity to be contextually managed;
    +    $build['#pre_render'][] = static::class . '::disableContextualLinks';
    +    // - Quick Edit does not make sense for embedded entities; we only allow the
    +    //   host entity to be edited in-place.
    +    $build['#pre_render'][] = static::class . '::disableQuickEdit';
    

    This seems like something that should be in the Contextual and Quick Edit modules, respectively. Not sure how we might go about that, though.

  7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +        'src' => file_url_transform_relative(file_create_url('core/modules/media/images/icons/no-thumbnail.png')),
    

    It'd be neat to make this configurable. Totally follow-up material, though.

  8. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +    if (stristr($text, 'drupal-media') !== FALSE) {
    

    Nit: We don't need !== FALSE. According to the PHP documentation, stristr() will return the substring if found, which means that it will be truthy.

  9. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +      foreach ($xpath->query('//drupal-media[@data-entity-type="media" and normalize-space(@data-view-mode)!="" and normalize-space(@data-entity-uuid)!=""]') as $node) {
    

    The value of data-entity-type should probably use the static::ENTITY_TYPE_ID constant. Also, I feel like this query should be generated in a helper method.

  10. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +        $build = $media === NULL
    +          ? $this->renderMissingMedia()
    +          : $this->renderMedia($media, $view_mode, $langcode);
    

    I find it strange that the "true" case here is $media === NULL. Can we maybe phrase this as $build = $media ? $this->renderMedia() : $this->renderMissingMedia()?

  11. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +        $this->renderIntoDomNode($build, $node, $result);
    

    I love that this is in its own method.

  12. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +   * @param array &$build
    +   *   The render array to render in isolation
    

    Why is $build a reference? That's inconsistent with the function signature.

  13. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +   * @param \DOMElement &$node
    +   *   The DOM node to render into. To be serialized later.
    

    Same here. Also, what does "To be serialized later" mean? Can we remove that bit?

  14. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +  private function renderIntoDomNode(array $build, \DOMElement $node, FilterProcessResult &$result) {
    

    The original Entity Embed filter protects against recursive rendering, and I think that we should probably do something similar here. I think we can defer it to a follow-up, but it's a thing we need to at least consider.

    Also, this is a useful method! I wonder if we should move it to one of the classes in the Utility component.

  15. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +   * @param \DOMNode $node
    +   *   A DOMNode object.
    

    I think the description could be better.

  16. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,285 @@
    +  private static function replaceNodeContent(\DOMNode &$node, $content) {
    

    Why is &$node a reference?

  17. +++ b/core/modules/media/tests/src/Functional/FilterTest.php
    @@ -0,0 +1,145 @@
    +    EntityViewMode::create([
    +      'id' => 'media.embed',
    +      'targetEntityType' => 'media',
    +      'status' => TRUE,
    +      'enabled' => TRUE,
    +      'label' => $this->randomMachineName(),
    +    ])->save();
    

    We should ship the new view mode in this patch, rather than create it here. Additionally, we will need to ship view displays for all standard media types, in both Standard and Umami.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
5.95 KB
14.85 KB

#30 Thanks for the review! 🙏

  1. ✅ (marked it final too)
  2. ✅ (instead removed the constant). I'm super fascinated by your proposal to pave the path for derivatives. I think we should KISS, and not make the code even the slightest bit more complex for future extensions. Beyond that, I think the whole point is that we reuse the existing entity render system, with view modes etc. If we introduce entity type-specific data- attributes, we move away from the "single way of declaring what you're embedding", and therefore make it harder to do transformations and migrations in the future. One of which would be to move on from <drupal-media> to <drupal-entity> (per #25, third point).
  3. 😀
  4. 🤔Maybe in the future. Definitely out-of-scope here.
  5. 👎Let's only do that when the need arises.
  6. 👎\Drupal\filter\Plugin\Filter\FilterCaption::process() does the same.
  7. ✅(discussed with @phenaproxima, the constant makes less sense now)
  8. 🙂
  9. 🤔If we're going to do that, we should do it in this patch IMHO. I'll do that next (early next week).
    I wanted to move this method into a utility class like you asked, but unfortunately it needs the renderer service, and \Drupal\Component\Utility\Html doesn't use the service container at all, and for good reason: it lives in \Drupal\Component.
  10. Because it is an object that gets updated by reference, so that when we serialize the DOM whose nodes we're modifying
  11. 👎👎👎 That would expand the scope massively. I just renamed it to avoid triggering this understandable reflex :)

    Also spotted some nits myself, fixed those too.

Wim Leers’s picture

Of course I find additional nits just after submitting 😅

Wim Leers’s picture

After an initial quick pass together with @alex.pott, I found some leftovers from an earlier patch iteration. Also added an explicit @todo for #30.14 (recursive rendering protection). And added more explicit @todos for the missing upstream test coverage that should be improved and then brought into this patch.

Wim Leers’s picture

Status: Needs review » Needs work

Walked through it properly with @alex.pott this time, he suggested:

  1. to not use final, because it's not an existing pattern
  2. to not use private, because it's not an existing pattern
  3. to use ->error(…) rather than ->log(RfcLogLevel::ERROR, …)

And last but not least: he said the filter should not generate config dependencies (on the used view modes etc) because that'd then require modifying existing content just to be able to modify config (to remove entity view modes & displays), the existing fallback behavior is preferable over that.

claudiu.cristea’s picture

So, embedding means injecting proprietary tags such as <drupal-media ...>? This is very sad. This kind of markup has no meaning outside Drupal. Other system doesn't know how to deal with tags such as <drupal-media ...>. In the case you want to achieve interoperability this is a NO GO. Why not injecting the rendered media which already have native tags (img, a, etc.) wrapped in this proprietary HTML tag has all he Drupal metadata (uuid, view mode) as data attributes? The wrapper will be just ignored on other systems but the inner markup can still be interpreted. That said the process of rendering the media will take place on the embedding phase, not in the filter

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.

Wim Leers’s picture

After a 4 month period of silence here due to my working on getting JSON:API to ship with Drupal 8.7, time to pick this up again!

That said the process of rendering the media will take place on the embedding phase, not in the filter

That's exactly why we don't inject rendered media. When the media in the library is updated, we need those changes to appear everywhere else too. That's how https://www.drupal.org/project/entity_embed works too.

Note that any consumer of the website never sees our custom HTML tags — the outside world only ever sees the rendered media just as you say!

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
4.21 KB
15.39 KB

This addresses @alex.pott's feedback from #27. Next up is test coverage, as described in #27, specifically these two bullets:

  • test coverage is super minimal for now, it's painful to write test coverage for now because Entity Embed's test coverage is doing rather rough HTML assertions, which are impossible to transform to Media entity HTML assertions because Media entities are rendered in more complex ways; we should first improve \Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest further
  • in-place editing and contextual links are explicitly disabled; the reason being that it's going to be a terrible UX if content authors can in-place edit embedded entities, because most of them will assume that will only affect that specific embed (to be ported to Entity Embed, and this will also affect its test coverage)
Wim Leers’s picture

Title: Add an input filter to display embedded Drupal entities » Add an input filter to display embedded Media entities

Updating title per #25.

andrewmacpherson’s picture

Status: Needs review » Needs work

Patch #38:

+  protected function renderMissingMedia() {
+    return [
+      '#type' => 'html_tag',
+      '#tag' => 'img',
+      '#attributes' => [
+        'src' => file_url_transform_relative(file_create_url('core/modules/media/images/icons/no-thumbnail.png')),
+        'width' => 180,
+        'height' => 180,
+        'alt' => $this->t('Deleted content encountered, site owner alerted.'),

Is this text alternative going to be presented publicly to a website visitor? The message here describes the inner working of the CMS, and governance process in the organization who runs it. A public visitor doesn't benefit from being told about this. In any case, "site owner alerted" is not conveyed by the image itself; we don't want to provide extra information to screen reader users which we haven't provided for a sighted user. Also "deleted" isn't the same as "missing".

This is a tricky situation to come up with alt text for. I'd suggest something simpler, like "missing image" or "broken image". This would be a more matter-of-fact statement, which doesn't say anything about the CMS or owner's processes. It would be good to tailor it to the media type ("image" or "video" instead of vague "content").

andrewmacpherson’s picture

Issue tags: +Accessibility
Wim Leers’s picture

Time for a big update. https://www.drupal.org/project/entity_embed has been stabilized 🥳 And comprehensive test coverage was added in #3052482: Expand EntityEmbedFilter's test coverage: test many more edge cases, change to kernel test.

Time to bring that all over in clear steps to this issue!

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
222.22 KB
1.85 KB
16.55 KB

Let's start with something simple: the visual brokenness you can see in #27.5's screenshot.

Before
After

Status: Needs review » Needs work

The last submitted patch, 43: 2940029-43.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.06 KB
17.57 KB

❤️ StableLibraryOverrideTest

Wim Leers’s picture

This ports all the test coverage related to foundations from #3052482: Expand EntityEmbedFilter's test coverage: test many more edge cases, change to kernel test. This includes test coverage for:

  • the absolute basics: data-view-mode is able to select a view mode and associated cacheability is bubbled
  • entity access is respected and associated cacheability is bubbled
  • graceful degradation when entities are deleted or manually crafted HTML with invalid UUIDs is specified — and of course sensible alt text per #40
  • only <drupal-media> tags are processed
  • the alignment and caption filters still work as expected, including when the media embed itself is a link

To be able to pass the above tests, three changes had to be made in the filter's code:

  1. The Entity Embed module implemented @andrewmacpherson's feedback from #40 in #3063705: Improve `alt` and `title` attributes for missing entity indicator, and has explicit test coverage for it.
  2. "consumed" attributes should be removed, just like \Drupal\filter\Plugin\Filter\FilterAlign and \Drupal\filter\Plugin\Filter\FilterCaption do.
  3. Any non-consumed attributes must be retained, to ensure that a next filter is able to act on them.
Wim Leers’s picture

#27 pioneered the disabling of Contextual Links and Quick Edit — that's where this core patch identified a gap in https://www.drupal.org/project/entity_embed and leaped ahead.

#27 included rudimentary test coverage for this in a functional test. But now we can do this in a kernel test, with more precision, and faster :)

Wim Leers’s picture

So far, we only covered things that were essentially already supported in #27. We're just adding test coverage so far and doing finishing touches it seems.

But three important features are still missing in the current patch.

The first of those three: per-embed overrides. The ability to have <drupal-media … alt="this alt text is better tuned for the context where this media is embedded"> 🥳And of course, it comes with test coverage.

Wim Leers’s picture

The second of those three: translations. Again, with test coverage of course.

Wim Leers’s picture

And last but not least: recursion protection.

Wim Leers’s picture

Assigned: Wim Leers » Unassigned
Priority: Normal » Major
Issue summary: View changes
FileSize
737.69 KB

Considering this is a blocker of #2801307: [META] Support WYSIWYG embedding of media entities which in turn is a blocker for #2834729: [META] Roadmap to stabilize Media Library, bumping priority to Major.

Also revamped the issue summary.

The last submitted patch, 46: 2940029-46.patch, failed testing. View results

The last submitted patch, 47: 2940029-47.patch, failed testing. View results

The last submitted patch, 48: 2940029-48.patch, failed testing. View results

The last submitted patch, 49: 2940029-49.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 50: 2940029-50.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
649 bytes
52.25 KB

D'oh. Something silly ruining an otherwise beautiful streak of green patches! 😞

Wim Leers’s picture

Fixing the CS violations I introduced while porting this.

Wim Leers’s picture

  1. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,399 @@
    + *   description = @Translation("Embeds media items using a custom HTML tag. If used in conjunction with the 'Align/Caption' filters, make sure this filter is configured to run after them."),
    ...
    + *   weight = 100,
    

    Note that the default weight already ensures this happens by default. If reviewers would like that, we could add the additional form validation callback that the Entity Embed module is adding, entity_embed_filter_format_edit_form_validate(), which goes through great lengths to ensure that the site builder cannot set up things incorrectly.
    In Entity Embed we even have extensive functional JavaScript tests that prove this validation logic is working correctly and providing actionable error messages in all scenarios.

  2. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,399 @@
    + * @internal
    

    This was done at the request of #30.2, but are we sure about that?

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,399 @@
    +      \Drupal::logger('media')->error('Recursive rendering detected when rendering embedded media: %entity_id. Aborting rendering.', [
    ...
    +          \Drupal::logger('media')->error('The media item with UUID "@uuid" does not exist.', ['@uuid' => $uuid]);
    ...
    +          \Drupal::logger('media')->error('The view mode "@view-mode-id" does not exist.', ['@view-mode-id' => $view_mode_id]);
    

    This should probably get logger.factory injected. Or not? Because this is only used for error cases, perhaps we ought to keep \Drupal::logger()? I'm not sure what the best practice is here.

  4. +++ b/core/modules/media/tests/src/Functional/FilterTest.php
    @@ -0,0 +1,141 @@
    +class FilterTest extends BrowserTestBase {
    

    This test coverage that #27 introduced is now obsolete, since the kernel tests cover everything this covers, and much more!

    (It made sense at the time, because Entity Embed had little test coverage, but that's no longer the case.)

This reroll addresses point 4, points 1, 2 and 3 are open questions to reviewers 😊

phenaproxima’s picture

If reviewers would like that, we could add the additional form validation callback that the Entity Embed module is adding, entity_embed_filter_format_edit_form_validate(), which goes through great lengths to ensure that the site builder cannot set up things incorrectly.

My opinion is that, generally, if it is even possible to set it up incorrectly to the point where users and site builders will be scratching their heads and wondering WTF, why did my site break...we should do something to protect people from stumbling into that position. That said, we don't need to go insanely far; we just need to clearly warn people if they are going to inadvertently break something.

This was done at the request of #30.2, but are we sure about that?

I'm still very much +1 for making the class internal. It's a lot easier for us to open it up later (hell, it could probably even be done in a patch release) than it is to walk back the decision to make it non-internal.

This should probably get logger.factory injected.

Unless it's really difficult, I'd prefer if it were injected.

The last submitted patch, 57: 2940029-57.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

The last submitted patch, 58: 2940029-58.patch, failed testing. View results

The last submitted patch, 59: 2940029-59.patch, failed testing. View results

Wim Leers’s picture

Fixed the last test failure, which wasn't an actual failure, just a use of deprecated code in a test's setUp().

Also fixed the last few coding standards violations.

This should probably get logger.factory injected.

Unless it's really difficult, I'd prefer if it were injected.

Done.

Wim Leers’s picture

If reviewers would like that, we could add the additional form validation callback that the Entity Embed module is adding, entity_embed_filter_format_edit_form_validate(), which goes through great lengths to ensure that the site builder cannot set up things incorrectly.

My opinion is that, generally, if it is even possible to set it up incorrectly to the point where users and site builders will be scratching their heads and wondering WTF, why did my site break...we should do something to protect people from stumbling into that position. That said, we don't need to go insanely far; we just need to clearly warn people if they are going to inadvertently break something.

Sure.

I wanted to keep this separate from essentials like #64 so that we can easily choose to not do this if we want.

Note that this test coverage hasn't gone through quite the same level of scrutiny as the test coverage above; if we decide we like the additional validation this adds, we can of course scrutinize that all we want :)

effulgentsia’s picture

This patch is looking really good!

  1. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,411 @@
    +      static::$recursiveRenderDepth[$recursive_render_id]++;
    

    At some point, we need to decrement this, right? Otherwise, this prevents rendering 20 total embeds of the same media entity even if they're not recursive. For example, if you have a View of 20 nodes that each embeds the same media entity.

  2. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,411 @@
    +    // - Contextual Links do not make sense for embedded entities; we only allow
    +    //   the host entity to be contextually managed;
    +    $build['#pre_render'][] = static::class . '::disableContextualLinks';
    +    // - Quick Edit does not make sense for embedded entities; we only allow the
    +    //   host entity to be edited in-place.
    +    $build['#pre_render'][] = static::class . '::disableQuickEdit';
    

    Given that at least these two features need to be conditional on whether the media is embedded or not, it seems likely to me that other modules (including contrib) will need to be as well. Therefore, is there an API we can add to support that? Not sure if a new hook would make sense, or whether we want to leverage existing view alter hooks and just need to add some #property to identify that this is embedded?
    Handling the contextual links here seems appropriate, since EntityViewBuilder is what adds them, but let's move the quickedit disabling to the quickedit module, so it can serve as a reference consumer of whatever API or #property that we add here.

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,411 @@
    +      '#type' => 'html_tag',
    +      '#tag' => 'img',
    

    Is there a reason we're not using '#theme' => 'image' here?

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,411 @@
    +      foreach ($xpath->query('//drupal-media[@data-entity-type="media" and normalize-space(@data-view-mode)!="" and normalize-space(@data-entity-uuid)!=""]') as $node) {
    

    If the tag name is drupal-media, why do we require (or even allow) the data-entity-type attribute?

    Why do we require the data-view-mode attribute, instead of using 'full' as a default if it's empty (to match the default of EntityViewBuilderInterface)?

  5. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,411 @@
    +        $media->{$image_field}->alt = $node->getAttribute('alt');
    +        $media->thumbnail->alt = $node->getAttribute('alt');
    ...
    +        $media->{$image_field}->title = $node->getAttribute('title');
    +        $media->thumbnail->title = $node->getAttribute('title');
    

    We're applying the same alt and title to two different images: the source image field and the thumbnail. Do we know for certain that both won't be rendered? If so, let's add a comment as to why we know that. Or, if both can be rendered, then is it sensible for the same alt and title to apply to both? If so, let's add a comment explaining why that's sensible/desired.

Berdir’s picture

Re 1, the problem is that we delayed rendering, we don't actually know when it will happen, maybe some trickery with a callback as I thought about in #2876709: Recursive rendering protection mistakenly kills repeated rendering, too, but the core entity reference formatter has the same problem, so possibly we can solve it for both things together in #2940605: Can only intentionally re-render an entity with references 20 times

Wim Leers’s picture

#66: Thanks 😊 And yay! 🥳

  1. I specifically made this use the same logic as core already does where it has recursive rendering protection: \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::viewElements(). Also: what @Berdir said in #67 — I'd rather have this follow a pre-existing pattern in core with a particular known problem rather than inventing something new here which suffers from a different set of problems :)
  2. ✅ Very good point! A property is declarative and hence is IMO preferable over introducing a hook. This never even crossed my mind since this originates from stabilizing a contrib module, where we obviously cannot change things in Drupal core for the better end result. So I'm very grateful you pointed this out 🙏
    Added the declarative property, removed ::disableQuickEdit(), updated quickedit_entity_view_alter().
  3. ✅ Not particularly. I don't have a preference. Switched to your suggestion.
  4. RE: data-entity-type: Excellent question :) Because years ago, before Drupal 8 shipped, Drupal core coordinated with Entity Embed (well, I coordinated with Dave Reid) to ensure we have a single consistent way to detect the presence of entities in what effectively are blobs of HTML. We agreed on data-entity-type and data-entity-uuid. That's how \Drupal\editor\Plugin\Filter\EditorFileReference is able to reliably generate file usage data. Except of course that there are lots of known problems with our file usage, entity usage, and entity dependency tracking. Nonetheless, by having this filter generate (and require) the same structure, we make it possible to in the future have a single coherent way of tracking usage. Without having to retroactively update all user-generated content. If you agree with this rationale — and I hope you do — we should document it in the patch.

    RE: data-view-mode: explicitness. Because there are lots of surprising aspects to this:

    1. This is a blob of HTML. Non-Drupal experts may look at it. It may be migrated at some point.
    2. Maybe the default of full on EntityViewBuilderInterface::view() disappears.
    3. full is a lie for Node entities: if you load full, you'll end up with default.
    4. This will be exposed to non-Drupal consumers via JSON:API, GraphQL and REST. We should not leave semantics open for interpretation when we know that we expect it to behave in a particular way in Drupal itself.

    Weighing the trade-off of this spectrum of potential problems versus having to specify data-view-mode="full", and taking into account the fact that nobody will be writing that HTML themselves manually since it'll be generated by #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library, I think it is prudent to be more specific.

  5. ✅ Comment added. Related: #2956368: MediaThumbnailFormatter produces unhelpful text alternative and title attributes for media thumbnails.
effulgentsia’s picture

+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -0,0 +1,401 @@
+    $build['#media_embed'] = TRUE;

Would it make sense to rename this to just #embed? In other words, is it likely that quickedit should not apply itself for all entity embeds, not just media? Note that there's already a separate #media, or more generically #ENTITY_TYPE, property present if alter hooks need to distinguish by that.

Out of curiosity, I searched HEAD to see if #embed is already a minted name, and it is, for Views embed displays. I'm not sure if that's a pro or con to using the same name here. I think it might be a pro, as it's roughly the same concept both here and in Views. But maybe it's a con if there's differences in the two concepts that matter.

effulgentsia’s picture

I haven't debugged this yet, so maybe there's just something weird going on for me locally, but posting this here anyway, in case it's a real bug...

When I follow the instructions in the IS, then it all works great when I view the node at its URL (e.g., /node/2). However, when I go back to the front page, and then see that article as a teaser, it just displays the word "and", but without rendering the media above or the missing media below.

effulgentsia’s picture

+++ b/core/modules/media/media.module
@@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) {
+      $required_attributes = [
+        'data-entity-type',
+        'data-entity-uuid',
+        'data-align',
+        'data-caption',
+        'alt',
+        'title',
+      ];

Per #68.4, 'data-view-mode' should be added to this list.

Why are data-align, data-caption, alt, and title in this list? What if I don't want to allow my content authors to provide captions, or to override the media item's alt and title?

effulgentsia’s picture

+++ b/core/modules/media/media.module
@@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) {
+    // Require `<drupal-media>` HTML tag if filter_html is enabled.

Is it possible to automatically add it when the Media embed filter is newly enabled (when I click its checkbox in the UI)? Or is there a reason that we shouldn't do that?

effulgentsia’s picture

when I go back to the front page, and then see that article as a teaser

That reminds me... Something that I would want if I were building a site is to be able to render the media in a different view mode based on the view mode of the host entity. For example, if I'm rendering the article in full, then maybe I want the embedded media in full. And if I'm rendering the article as a teaser, then maybe I want to render the embedded media as a teaser/thumbnail/whatever. In fact, I might want to enforce this, and not allow content authors to specify a data-view-mode attribute when inserting the media. That way, presentation decisions are kept in the Manage Display configurations (where they belong), and not inside the content.

Maybe it makes sense to punt that to contrib? But I'm not sure, because I'd rather that core encourage best practices, and I'm not sure if it's best practice for core to steer people into making presentation choices inside their content. For example, when using a Media field, content authors are not able to pick the view mode as part of adding media to that field: they can only do that via the field's formatter.

Berdir’s picture

> That reminds me... Something that I would want if I were building a site is to be able to render the media in a different view mode based on the view mode of the host entity.

To be honest, I feel like that level of control requires different tools. e.g. layout builder, paragraph-based approaches or just simply separate fields and fixed layouts, which is how we almost always handle teasers. IMHO, trying to use (automatically) trimmed versions of text for teasers almost never works, even when it's just text, and less so when you have more complex elements like embedded entities in there.

effulgentsia’s picture

I feel like that level of control requires different tools

Hm, maybe so. But I still feel like requiring the data-view-mode attribute steers people down the wrong path. What if we make it optional, and add a default_view_mode setting to the MediaEmbed filter? That way, site builders who want to keep presentation decisions out of their content can do so (by not adding data-view-mode as an allowed attribute on <drupal-media>), while site builders who want to expose the ability for content authors to pick the view mode on a per-embed basis can add it as an allowed attribute.

Berdir’s picture

I think that makes sense, if we have a setting we could also make it a multi-selection, because even *if* you want to allow access to select a view mode, there's about a 99% chance that you only want to allow a few embed specific view modes that make sense. The entity_embed module handles that through its buttons & plugins, not on the filter level.

dww’s picture

+1 to all this, especially both #75 and #76.

Excited to see this moving forward. Hope to have time for a more thorough review and testing, we'll see.

Thanks,
-Derek

phenaproxima’s picture

Partial review...

  1. +++ b/core/modules/media/media.libraries.yml
    @@ -23,3 +23,11 @@ oembed.frame:
    +filter.caption:
    +  version: VERSION
    +  css:
    +    component:
    +      css/filter.caption.css: {}
    +  dependencies:
    +    - filter/caption
    

    Doesn't this mean we need to make Filter an explicit dependency of Media?

  2. +++ b/core/modules/media/media.module
    @@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) {
    +  // This validate handler is not applicable when using the 'Configure' button.
    +  if ($form_state->getTriggeringElement()['#name'] === 'editor_configure') {
    +    return;
    +  }
    

    Kind of a nitpick, but: I think we should change the logic here so that we are only doing the validation if the correct button is pressed, rather than early-returning if the wrong button is pressed. That way, we are future-proofed against the possibility of new buttons being added to the form.

  3. +++ b/core/modules/media/media.module
    @@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) {
    +  $filter_html_enabled = $form_state->getValue([
    +    'filters',
    +    'filter_html',
    +    'status',
    +  ]);
    

    Shouldn't this be $form_state->getValue($filter_html_settings_path)?

  4. +++ b/core/modules/media/media.module
    @@ -359,3 +359,142 @@ function media_entity_type_alter(array &$entity_types) {
    +  $get_filter_label = function ($filter_plugin_id) use ($form) {
    +    return (string) $form['filters']['order'][$filter_plugin_id]['filter']['#markup'];
    +  };
    

    I wonder if it might not make more sense to get the filter label directly from the plugin definition, rather than from the form structure.

  5. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +   * @param \Drupal\media\MediaInterface $media
    +   *   A media entity for which to render the 'embed' view mode.
    +   * @param string $view_mode
    +   *   The view mode to render it in.
    

    🤔The comment for $media suggests that the method has no choice about which view mode to use, but that implication is contradicted by the comment for $view_mode. :)

  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +    if (static::$recursiveRenderDepth[$recursive_render_id] > EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT) {
    

    20 recursive renders seems like a lot for embedded media. I can understand that for regular entity references, but perhaps a lower limit, like 10, would be more appropriate here?

  7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +    // There are a few concerns when rendering an embedded media entity:
    +    // - entity access checking happens not during rendering but during routing,
    +    //   and therefore we have to do it explicitly here for the embedded entity;
    +    $build['#access'] = $media->access('view', NULL, TRUE);
    

    Should we do this before building the render array, and return an empty array early?

  8. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +    // - default styling may break captioned media embeds; attach asset library
    +    //   to ensure captions behave as intended.
    +    $build[':media_embed']['#attached']['library'][] = 'media/filter.caption';
    

    Why is the library attached under a special :media_embed key? Not saying it's wrong, but maybe a comment would be helpful. :)

  9. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +    if (stristr($text, 'drupal-media') !== FALSE) {
    

    Three things: 1) Can we early return instead of putting the entire function body in an if statement; 2) I think we could use strstr() here, since the drupal-media tag should always be lowercase; and 3) We should probably search for <drupal-media, just so we avoid edge cases where "drupal-media" is a legitimate piece of the input text.

  10. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,401 @@
    +  protected static function replaceNodeContent(\DOMNode &$node, $content) {
    

    Does this need to be static?

AaronMcHale’s picture

Big +1 from me to giving the site builder more control over which view modes are available to content authors, and forcing a specific one, it would enhance and be in line with #784672: Allow text field to enforce a specific text format.

effulgentsia’s picture

20 recursive renders seems like a lot for embedded media. I can understand that for regular entity references, but perhaps a lower limit, like 10, would be more appropriate here?

See #66.1 and #67: Until those issues are fixed, this number limits the total renders (per page request) of the same media entity, whether recursive or not. So -1 to reducing it, at least until it's solely a recursion limit.

Wim Leers’s picture

#69: 🥳✅Changed it to '#embed' => TRUE.


#70: I cannot reproduce this. I just see the first embedded media.


#71: Good catch on data-view-mode — that's a mistake I made in the porting of #65. WRT data-align & friends: like I wrote in #65: this test coverage hasn't gone through quite the same level of scrutiny . For now, omitted those.
Related to that: we could make data-align required if the filter_align filter is enabled, and do the same for data-caption + filter_caption. OTOH, it is possible that one wants alignment/captions for <blockquote>s and not for <drupal-media>. So given that, I think we should stick to what's in the current patch, and then just ship Standard with sane defaults.


#72: Excellent question! 🙏

The same challenge is already present in Drupal core when enabling the filter_align, filter_caption or editor_file_reference @Filter plugin.

Does that convince you that we shouldn't do it here either, or do you think we should try to do that anyway?


#73: If I understand your comment correctly, you're wondering if we shouldn't enforce a single view mode by default? If so: YES PLEASE! 👏👏👏👏👏👏 This is exactly what I proposed during the Barcelona Media Sprint in December, and it's documented in the 4th bullet of #25. I personally believe this would be a massive improvement: choosing a view mode for embedded media is cool, but how often does it really make sense to embed media in Full or Media Library view mode (the two defaults core ships with)? And even if we add an Embed view mode by default, when does it make sense to select one of those other two?
I totally see how a Tiny embed and Detailed embed could be valuable. But that is a relatively advanced use case — used by perhaps 10 or 20% of Drupal 8 sites. Why not defer that to a media_embed_viewmode contrib module?
This would simplify so much. 🙏

#74: I think you too are leaning towards something I suggested above for #73?

#75: Or why not defer this to a contrib module that decorates the existing plugin implementation?

#76: Correct, but this means that even though the Entity Embed contrib module's user interface only allows say view modes X and Y, its filter still allows anything. I think you would agree that in core we would stronger guarantees about what presentation-level choice is available in the structured data that is generated?


#78:

  1. No, because this asset library is only ever used by \Drupal\media\Plugin\Filter\MediaEmbed. Put differently: it's only ever used if the filter module uses the @filter plugin we provide, and in that case the filter/caption asset library will also exist. Very sharp observation though! 👍
  2. 🦅👁✅ Done, although arguably now if somebody adds another submit button, then they won't get the validation anymore. But this seems ridiculously far-fetched. I agree your suggestion hardens the situation.
  3. No, this is not getting the settings (allowed_html) for filter_html, but whether it's turned on or off.
  4. 🤔Your second point was about playing well with form alters. This ensures form alters making label alterations are respected. So wouldn't it be better to keep this as-is?
  5. 🦅👁🤔! Do you have a suggestion for how to fit all that in 80 cols?
  6. I agree with @effulgentsia in #80 — I just want to keep this the same as other places in core with the same logic.
  7. Hm … I guess we could. But the end result would be the same. This feels like premature optimization to me. I'd rather have us generate a declarative result (a render array) and then rely on all the existing infrastructure to do this for us. That's also what we do in many (most?) other places in Drupal core. I don't feel very strongly about this though. I wonder what @effulgentsia thinks?
  8. ✅ Comment added. (To convince yourself: remove that top-level key and run \Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testAccessUnpublished(), observe it fail.)
  9. ✅ 1) Of course! Done., 2) We can't assume it will always be lowercase. 3) Done.
  10. http://drupal4hu.com/node/416.html :)

Also renamed MediaFilterConfigurationUiTest to MediaEmbedFilterConfigurationUiTest.


#79: Cool, that sounds like another vote for my proposal in #73. Looking forward to your thoughts on my proposal too 😊


#80: +1

phenaproxima’s picture

Status: Needs review » Needs work

Another partial review. I didn't get all the way through the tests, but I got all the way through the filter. I think it looks really, really good.

  1. +++ b/core/modules/media/media.module
    @@ -359,3 +359,138 @@ function media_entity_type_alter(array &$entity_types) {
    +    // A filter that should run before media embed filter.
    +    $precedent = $filters[$filter_name];
    

    Can we be certain that $filters[$filter_name] will exist? Should we do something if it doesn't?

  2. +++ b/core/modules/media/media.module
    @@ -359,3 +359,138 @@ function media_entity_type_alter(array &$entity_types) {
    +      '%media-embed-filter-label' => $get_filter_label('entity_embed'),
    

    The machine name of the filter is media_embed, not entity_embed, so this error message is probably not right and we are missing a bit of test coverage :)

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    + * Provides a filter to embed Media items using a custom tag.
    

    Supernit: should be "media".

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +   * @param \Drupal\media\MediaInterface $media
    +   *   A media entity for which to render the 'embed' view mode.
    

    This comment should just say "A media entity to render." The view mode is specified by the $view_mode parameter.

  5. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +    // There are a few concerns when rendering an embedded media entity:
    +    // - entity access checking happens not during rendering but during routing,
    +    //   and therefore we have to do it explicitly here for the embedded entity;
    +    $build['#access'] = $media->access('view', NULL, TRUE);
    +    // - caching an embedded media entity separately is unnecessary; the host
    +    //   entity is already render cached;
    +    unset($build['#cache']['keys']);
    +    // - Contextual Links do not make sense for embedded entities; we only allow
    +    //   the host entity to be contextually managed;
    +    $build['#pre_render'][] = static::class . '::disableContextualLinks';
    +    // - default styling may break captioned media embeds; attach asset library
    +    //   to ensure captions behave as intended. Do not set this at the root
    +    //   level of the render array, otherwise it will be attached always,
    +    //   instead of only when #access allows this media to be viewed and hence
    +    //   only when media is actually rendered.
    +    $build[':media_embed']['#attached']['library'][] = 'media/filter.caption';
    

    I love these comments. So clear and helpful. This right here is the Wim Leers Special and it's what makes you such a pleasure to code with.

  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +      if (!$media) {
    +        $this->loggerFactory->get('media')->error('The media item with UUID "@uuid" does not exist.', ['@uuid' => $uuid]);
    +      }
    

    I think this error message should mention that we were trying to render the media item as an embed.

  7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +      if (!$view_mode) {
    +        $this->loggerFactory->get('media')->error('The view mode "@view-mode-id" does not exist.', ['@view-mode-id' => $view_mode_id]);
    +      }
    

    Same here?

  8. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
    @@ -0,0 +1,159 @@
    +/**
    + * @covers ::media_filter_format_edit_form_validate
    

    I didn't know it was possible to specify function-specific coverage this way. TIL :)

  9. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
    @@ -0,0 +1,159 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function setUpBeforeClass() {
    +    parent::setUpBeforeClass();
    +    // Necessary for @covers to work.
    +    require_once __DIR__ . '/../../../media.module';
    +  }
    

    Hmmm...is there precedent for this elsewhere in core? If not, I'd prefer to leave it out and not bother with @covers annotations.

  10. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
    @@ -0,0 +1,159 @@
    +    $this->assertSession()->buttonExists('Save configuration')->press();
    

    This should be $page->pressButton('Save configuration').

  11. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
    @@ -0,0 +1,159 @@
    +    $this->assertSession()->buttonExists('Save configuration')->press();
    

    Same here.

  12. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
    @@ -0,0 +1,159 @@
    +  public function providerTestValidations() {
    

    I don't see any test cases for when the filter weights are incorrect...?

phenaproxima’s picture

  1. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterDisabledIntegrationsTest.php
    @@ -0,0 +1,66 @@
    +  /**
    +   * @covers \Drupal\media\Plugin\Filter\MediaEmbed::renderMedia
    +   * @covers \Drupal\media\Plugin\Filter\MediaEmbed::disableContextualLinks
    

    Nit: we don't need the FQCN here, since the @coversDefaultClass in the class doc comment provides that already.

  2. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterDisabledIntegrationsTest.php
    @@ -0,0 +1,66 @@
    +    $this->assertCount(0, $this->cssSelect($integration_detection_selector));
    

    This assertion seems a bit loose; if the filtered text fails to render the embedded media item, I can see this producing false positives. If I wonder if we should explictly assert that the div[data-media-embed-test-view-mode] element exists, and then assert that the $disabled_integration_selector is not present.

  3. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +    $this->assertSame(['library'], array_keys($result->getAttachments()));
    

    I think this assertion is superfluous; it's covered by the next one.

  4. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +      ->setCacheMaxAge(Cache::PERMANENT);
    

    This isn't technically needed, but +1 for explicitness!

  5. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +    // Expected bubbleable metadata.
    

    Nit: I don't think this comment adds much. :)

  6. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +  public function testOverridesAltAndTitle() {
    

    I don't see where we're testing that title is ignored if the title_field setting is turned off...?

  7. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +    // Render a 21st time, this is exceeding the recursion limit. The entity
    +    // embed markup will be stripped.
    +    $this->applyFilter($text);
    +    $this->assertEmpty($this->getRawContent());
    

    Should we also set up a mocked logger in the container, to ensure that an error is logged in this case?

  8. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -0,0 +1,431 @@
    +  /**
    +   * @covers \Drupal\filter\Plugin\Filter\FilterAlign
    +   * @covers \Drupal\filter\Plugin\Filter\FilterCaption
    +   * @dataProvider providerFilterIntegration
    +   */
    +  public function testFilterIntegration(array $filter_ids, array $additional_attributes, $verification_selector, $expected_verification_success, array $expected_asset_libraries, $prefix = '', $suffix = '') {
    

    These @covers annotations aren't really accurate, since we're not explicitly covering either of those classes.

  9. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTestBase.php
    @@ -0,0 +1,247 @@
    +  protected function processText($text, $langcode = 'und', array $filter_ids = ['media_embed']) {
    

    Should we use \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED for $langcode?

  10. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
    @@ -0,0 +1,90 @@
    + * Tests that media embeds are translated based on text (host entity) language.
    

    Can we just say "...based on the host entity's language"?

  11. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
    @@ -0,0 +1,90 @@
    +    ConfigurableLanguage::createFromLangcode('pt-br')->save();
    

    Obrigada!

  12. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
    @@ -0,0 +1,90 @@
    +      yield "text_langcode=$text_langcode ⇒ $match_or_fallback_langcode" => [
    +        $text_langcode,
    +        $match_or_fallback_langcode,
    +      ];
    

    ...I didn't know you could yield inside a data provider. O_O

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
9.08 KB
60.64 KB

#82:

  1. Good question. Yes, we can be certain of that. Because the three filters being checked here are all provided by filter.module and are therefore always present.
  2. 🦅👁🙏✅ Fixed! I disagree with missing test coverage. We don't want to have test coverage for every subset of every string. Then Drupal would be at least 10x bigger.
  3. ✅ Right, that's a remnant from an earlier iteration where we didn't have data-view-mode. Removing data-view-mode is being discussed again ironically :)
  4. ❤️ — Trying to help future maintainers, which may be I, or it may be somebody who's yet to discover Drupal!
  5. 👍 (Had to look this up too by the way!)
  6. There is: \Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTest::setUpBeforeClass() + \Drupal\Tests\block\Kernel\BlockRebuildTest::setUpBeforeClass().
  7. Oh, hah! You're absolutely right. We missed that in #3060749: entity_embed_filter_format_edit_form_validate() Logic is Faulty too apparently. Excellent catch! Will address that tomorrow.

#83:

  1. 🤔✅ Hm … I'm not quite convinced by this, because we know from MediaEmbedFilterTest's passing that filtering is not failing. But still, I see your point. Done.
  2. 👎 Close, but not quite! getAttachments() could also return attachments other than library. See \Drupal\Core\Render\HtmlResponseAttachmentsProcessor::processAttachments(). This asserts that only one type of attachment exists: library. The next assertion asserts that only the expected values for the library attachment type exist. Very subtle, I know :)
  3. 👍
  4. You're right, currently we're only testing that it's possible to override these. I'll add test coverage for this tomorrow.
  5. 🤔 I personally don't think that's worth testing.
  6. 👎 We are. The $verification_selectors 'figure > figcaption', 'div[data-media-embed-test-view-mode].align-center', 'figure > a[href="https://www.drupal.org"] + figcaption', 'a[href="https://www.drupal.org"] > div[data-media-embed-test-view-mode].align-center' etc assert the processing of FilterAlign and FilterCaption have happened.
  7. 👍✅ Good idea!
  8. 👎 No because that would be inaccurate. The filter plugin has no knowledge of the host entity at all. It only receives the text language. And the text language happens to match the host entity's language. That's why I chose this specific wording. Given that extra info, do you agree with it now?
  9. 🇧🇷 Hah! I think this is thanks to @marcoscano 🥳
  10. Anywhere you're returning an array you can use it :)

Tomorrow: #82.12 and #83.6

Wim Leers’s picture

Add missing test coverage discovered in #82.12.

Wim Leers’s picture

Add missing test coverage discovered in #83.6.

Wim Leers’s picture

+++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
@@ -213,21 +215,38 @@ public function testOverridesAltAndTitle() {
+      '`title` field property enabled ⇒ `title` is not overridable' => [

Nit: s/is not/is/

tstoeckler’s picture

Status: Needs review » Needs work

Only reviewed the non-test parts. Really impressive stuff, had a couple comments that @phenaproxima had above as well that were already answered so removed those and only a couple minor things remain. Really impressive work!

  1. +++ b/core/modules/media/media.module
    @@ -359,3 +359,138 @@ function media_entity_type_alter(array &$entity_types) {
    +    $filter_format = $form_state->getFormObject()->getEntity();
    ...
    +    $filter_html = $filter_format->filters()->get('filter_html');
    +    $filter_html->setConfiguration(['settings' => $form_state->getValue($filter_html_settings_path)]);
    

    I think it is strange to be updating the entity stored in the form object here. If it does not already have the submitted form values at this point I don't think we should change them here, so I think we need to clone the filter before modifying it.

  2. +++ b/core/modules/media/media.module
    @@ -359,3 +359,138 @@ function media_entity_type_alter(array &$entity_types) {
    +    $singular = 'The %media-embed-filter-label filter needs to be placed after the %filter filter.';
    +    $plural = 'The %media-embed-filter-label filter needs to be placed after the following filters: %filters.';
    +    $error_message = \Drupal::translation()->formatPlural(count($error_filters), $singular, $plural, [
    +      '%media-embed-filter-label' => $get_filter_label('media_embed'),
    +      '%filter' => reset($error_filters),
    +      '%filters' => implode(', ', $error_filters),
    +    ]);
    

    Nice idea to use formatPlural! Unfortunately you cannot split out the messages into separate variables because the PO extractor will not recognize the strings that way, making them in effect untranslatable.

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +    // need to generate a counter that takes into account all the
    +    // relevant information about this field and the referenced entity
    +    // that is being rendered.
    ...
    +    $recursive_render_id = $media->uuid();
    

    But that's not really true, is it? In particular the field part...

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -0,0 +1,406 @@
    +        $media->{$image_field}->alt = $node->getAttribute('alt');
    ...
    +        $media->thumbnail->alt = $node->getAttribute('alt');
    ...
    +        $media->{$image_field}->title = $node->getAttribute('title');
    +        $media->thumbnail->title = $node->getAttribute('title');
    

    The media entity might be in the static cache of the entity storage, so we should clone it before modifying it, otherwise these changes may leak to unexpected places.

effulgentsia’s picture

When I go back to the front page, and then see that article as a teaser, it just displays the word "and", but without rendering the media above or the missing media below.

#70: I cannot reproduce this. I just see the first embedded media.

I found out what was happening. What I was testing when reporting that was a YouTube video as the 1st media item. And the problem with that is that it renders as an <iframe>. TextTrimmedFormatter::preRenderSummary() then trims that, which converts it from a MarkupInterface object to a plain string, and assigns it to #markup. Because it's now a plain string, Renderer::ensureMarkupIsSafe() strips out the iframe.

I'm not sure what the correct fix for this is. Maybe text_summary() needs to return a MarkupInterface object if it is passed one. Though we'd have to think about whether that's a secure thing to do. If something along those lines is the proper fix, then that can be a separate issue that doesn't need to block this one.

Wim Leers’s picture

#89: Oh, fascinating! Reproduced. With no content in a body field other than an embedded video, I see this:

… that's indeed very broken. Not only does the video not show up, the "node links" end up in something that sort of looks like a caption. 🤯 Turns out the HTML is completely broken. The first bit looks okay:

<div property="schema:text" data-quickedit-field-id="node/9/body/en/teaser" class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><figure role="group" class="caption caption-drupal-media">
<article data-align="center" class="media media--type-remote-video media--view-mode-full">
  
      
            <div data-quickedit-field-id="media/5/field_media_oembed_video/en/full" class="field field--name-field-media-oembed-video field--type-string field--label-hidden field__item">

… but what then follows isn't the remainder of the embedded media, but an automatically inserted closing tag (albeit only for the last opening tag, not for the previous ones), followed by the "node links" HTML:

</div>
        <div class="node__links">
    <ul class="links inline"><li class="node-readmore"><a href="/node/9" rel="tag" title="video embed test" hreflang="en">Read more<span class="visually-hidden"> about video embed test</span></a></li><li class="comment-add"><a href="/node/9#comment-form" title="Share your thoughts and opinions." hreflang="en">Add new comment</a></li></ul>  </div>

Ouch.

This is not caused by this issue, it's a pre-existing bug. But once this lands, it'll be a problem many people will encounter. But precisely because it's a pre-existing issue, I can't imagine nobody else has run into this. So I searched. And my search attempt didn't disappoint, although it sure is frightening.
I present to you #221257: text_summary() should output valid HTML and Unicode text, and not count markup characters as part of the text length. Let me point out that this is a six-digit node ID. Yes, it dates back to a long time ago. Nearly 11.5 years ago to be precise 😨 Right in the issue summary it says this:

The current code generates invalid HTML, such as summaries that end in the middle of a closing tag. In fact, current tests require this mis-behavior, as does the WYSIWYG module.

This is exactly what's happening! The last significant work on this happened more than four years ago.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
3.39 KB
66.53 KB

#88: Thanks for the kind words! :) But remember, this is built off years of work by the https://www.drupal.org/project/entity_embed maintainers. It's much easier to see a clean path forward when the v1 was already written and a cleaner, simpler v2 can be extracted from it :)

  1. ✅ — I remember pointing this out in a review but apparently that never was addressed. Easy fix :)
  2. 🦅👁✅ — A mistake I made in porting to core!
  3. 🦅👁✅
phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

To be honest, I don't see a whole lot of reason not to go ahead with this now.

The filter works beautifully; it is as simple as it can be, the scope is well thought out, and several of the more "common" edge cases are handled. It is based on battle-tested code and concepts from Entity Embed. It has extensive test coverage and has been looked over by a framework manager. I don't doubt we'll find some more things as we continue to iterate on this, but as a solid foundation to build the WYSIWYG integration on, I think this is great work.

Let's land it.

Wim Leers’s picture

Assigned: Unassigned » effulgentsia
Status: Reviewed & tested by the community » Needs review

Woah! YAY! Thanks, @phenaproxima!

But … I'm going to do something unexpected and un-RTBC it myself, because I think we should answer @effulgentsia's concern in #73 wrt data-view-mode (and the ensuing discussion between @effulgentsia and @Berdir until #76). I responded to that in #81. Let me quote those two comments in full, since that is AFAICT the only remaining concern and those two comments capture the key points and potential solutions, so it makes sense to not force every reader who's following along to be scrolling like a maniac :P

@effulgentsia in #73:

when I go back to the front page, and then see that article as a teaser

That reminds me... Something that I would want if I were building a site is to be able to render the media in a different view mode based on the view mode of the host entity. For example, if I'm rendering the article in full, then maybe I want the embedded media in full. And if I'm rendering the article as a teaser, then maybe I want to render the embedded media as a teaser/thumbnail/whatever. In fact, I might want to enforce this, and not allow content authors to specify a data-view-mode attribute when inserting the media. That way, presentation decisions are kept in the Manage Display configurations (where they belong), and not inside the content.

Maybe it makes sense to punt that to contrib? But I'm not sure, because I'd rather that core encourage best practices, and I'm not sure if it's best practice for core to steer people into making presentation choices inside their content. For example, when using a Media field, content authors are not able to pick the view mode as part of adding media to that field: they can only do that via the field's formatter.

@Wim Leers in #81:

#73: If I understand your comment correctly, you're wondering if we shouldn't enforce a single view mode by default? If so: YES PLEASE! 👏👏👏👏👏👏 This is exactly what I proposed during the Barcelona Media Sprint in December, and it's documented in the 4th bullet of #25. I personally believe this would be a massive improvement: choosing a view mode for embedded media is cool, but how often does it really make sense to embed media in Full or Media Library view mode (the two defaults core ships with)? And even if we add an Embed view mode by default, when does it make sense to select one of those other two?
I totally see how a Tiny embed and Detailed embed could be valuable. But that is a relatively advanced use case — used by perhaps 10 or 20% of Drupal 8 sites. Why not defer that to a media_embed_viewmode contrib module?
This would simplify so much. 🙏

Assigning to @effulgentsia.

phenaproxima’s picture

In my opinion, we should support the data-view-mode attribute, but not provide a UI in core for changing its value. Leave that to contrib. I think people will appreciate that "hidden" flexibility.

@effulgentsia said this in #73:

and I’m not sure if it’s best practice for core to steer people into making presentation choices inside their content

In my view, I don't think that the data-view-mode attribute is steering people into doing anything. It is a way for us to provide strong, sensible default behavior and a hidden mechanism for tweaking that default. I think that's very much "the Drupal way".

I totally agree with Wim that many view modes, like "Full content" or "Teaser", just don't make sense in an embedded context. That's why we should provide an "Embed" view mode, and that should be the default value assigned to data-view-mode. If people feel strongly enough to change it, they will go into source mode and do that (or a contrib module which allows an author to set this value will probably arise). Fine, I say -- people do all kinds of weird things for all kinds of reasons. Why try too hard to prevent them from doing those things?

So, to summarize, I think we should go for a "provide a strong and sane default behavior, but not hard enforcement" approach. Let's ship an "Embed" view mode and always make the data-view-mode attribute default to that. Where site builders go from there, is their business. :)

There -- that's my $.02!

bkosborne’s picture

I don't think site admins wanting to provide multiple view mode options is an edge case. We're using entity embed heavily in D8 with lots of view modes. For example, if an image entity is selected for embed, we have options to control the size of the image embed. We also have an "image group" media entity, and we have view modes to control how the images should be arranged (slider? cluster? stacked?).

That said, I think that just means I'm in agreement with the previous comments! The view mode will be customizable in the embed token. We can further discuss the ability to select view mode in #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library (maybe punting to contrib).

effulgentsia’s picture

Assigned: effulgentsia » Unassigned
Status: Needs review » Needs work

For me, the key for this issue is that we should not require the data-view-mode attribute. In other words, these code hunks:

+++ b/core/modules/media/media.module
@@ -359,3 +359,141 @@ function media_entity_type_alter(array &$entity_types) {
+      $required_attributes = [
...
+        'data-view-mode',
+      ];
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -0,0 +1,405 @@
+    foreach ($xpath->query('//drupal-media[@data-entity-type="media" and normalize-space(@data-view-mode)!="" and normalize-space(@data-entity-uuid)!=""]') as $node) {

In order to not require it, we need a default. Probably the simplest way to represent that default is as a setting for the filter per #75. Unless there's other ideas for where/how to represent that default?

I think #76 is potentially worth doing as well, but that would be a separate setting (constraining the options rather than applying a default when no option is selected), but IMO, it's fine for that second setting to be added in a followup.

Setting to NW, but I'm also open to arguments for why we need the attribute to be required. I don't think I've heard/read any, but maybe I missed something in the previous comments?

Wim Leers’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Needs followup
FileSize
44.44 KB
13.91 KB
67.94 KB

but I'm also open to arguments for why we need the attribute to be required. I don't think I've heard/read any, but maybe I missed something in the previous comments?

I actually provided those reasons in #68. I'll repeat my answer here:

RE: data-view-mode: explicitness. Because there are lots of surprising aspects to this:

  1. This is a blob of HTML. Non-Drupal experts may look at it. It may be migrated at some point.
  2. Maybe the default of full on EntityViewBuilderInterface::view() disappears.
  3. full is a lie for Node entities: if you load full, you'll end up with default.
  4. This will be exposed to non-Drupal consumers via JSON:API, GraphQL and REST. We should not leave semantics open for interpretation when we know that we expect it to behave in a particular way in Drupal itself.

Weighing the trade-off of this spectrum of potential problems versus having to specify data-view-mode="full", and taking into account the fact that nobody will be writing that HTML themselves manually since it'll be generated by #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library, I think it is prudent to be more specific.

I am not convinced those reasons are more important than the simplicity of making data-view-mode optional. So I think @effulgentsia's proposal makes sense.


Overall, #96 is in line with what I anticipated @effulgentsia would say 😊

However, if we're going to elect a default, we then also need to agree in this issue what that default would be. Currently the Media module only ships with a single default view mode: full. That view mode is designed for the canonical route for media entities. Yes, as of #3017935: Media items should not be available at /media/{id} to users with 'view media' permission by default , the "standalone URL" is no longer available — see https://www.drupal.org/node/3018742. But that view mode is still inextricably coupled to that. It is also that name that is used for all other entity types and hence comes with a certain expectation.

(Installing the Media Library module adds another view mode for media entities: media_library. But that one also isn't a good fit for embedding. And the Media module can't rely on a view mode that only exists if another module is installed.)

That means that if we want to choose a default without expanding the scope of this patch, we can only choose full. So did that. That means now this appears in the text format configuration UI when the media_embed filter is enabled:

I personally still think we should add an Embed view mode, but deciding on the details of each of the corresponding view displays will likely involve bikeshedding, so I think we should defer that to a follow-up issue. Tagged Needs followup.

So IMHO next steps are:

  1. People agreeing with @effulgentsia and hence this patch can RTBC this patch 🤞🥳
  2. If we also agree we need as new view mode, we can create that follow-up issue. If we disagree, and want to stick to full as the default, then we can just remove Needs followup.

Status: Needs review » Needs work

The last submitted patch, 97: 2940029-97.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Wim Leers’s picture

Failing due to

Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was Drupal\media\Plugin\Filter\MediaEmbed::disableContextualLinks. Support for this callback implementation is deprecated in 8.8.0 and will be removed in Drupal 9.0.0. See https://www.drupal.org/node/2966725

which was introduced in #2966327: Limit what can be called by a callback in render arrays to reduce the risk of RCE just a few days ago.

(Also fixed coding standards violations.)

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

The interdiffs look good and make sense to me. I agree with @effulgentsia's proposal, and I appreciate the added test coverage (of course :)

I think this is ready. I'm making it RTBC now and will file a follow-up to explore the possibility of adding a new view mode.

phenaproxima’s picture

oknate’s picture

Excellent work here. I reviewed the code and ran the tests tonight. The only thing I could find were minor language nits.

1) Not a blocker, but I noticed the keys are inconsistent in MediaEmbedFilterConfigurationUiTest::providerTestValidations(), about half way through it switches from keys like this:

        'filters[filter_html][status]' => FALSE,
        'filters[filter_align][status]' => FALSE,
        'filters[filter_caption][status]' => FALSE,
        'filters[filter_html_image_secure][status]' => FALSE,

to keys like this:

        'filter_html' => TRUE,
        'filter_align' => TRUE,
        'filter_caption' => TRUE,
        'filter_html_image_secure' => TRUE,

I know it doesn't really matter, but I think it would be nicer to be consistent. I'm looking for things that could be improved.

2) minor nit, I think considering that the only entity type for embedded media is media, that calling it an embedded media media is redundant. I would change the key here to 'user cannot access embedded media' or 'user cannot access embedded media entity'. This is especially true because they next key doesn't have the redundancy, but reads 'user can access embedded media' .

+  /**
+   * Data provider for testAccessUnpublished().
+   */
+  public function providerAccessUnpublished() {
+    return [
+      'user cannot access embedded media media' => [

3) in MediaEmbedFilterTranslationTest::testTranslationSelection(), the help text is a bit confusing due to the use of the word "either":

a particular translation of the embedded entity is selected based on either the host entity's language, which should require a cache context to be associated.

I would expect an "or" conjunction to follow the use of "either". Should the word "either" just be dropped? "a particular translation of the embedded entity is based on the host entity's language, which should require a cache context to be associated."

4) Another minor nit about comment verb usage.

// Any attributes not consumed by the filter should be persisted onto
// the rendered embedded entity. For example, `data-align` and
// `data-caption` should persist,

The usage "to be persisted" is unidiomatic, albeit clear enough. Persist is something something does, not something that is done to something. I would suggest this usage:

"should be carried over to the rendered embedded entity. For example, `data-align` and `data-caption` should be carried over.

Also since the attributes are being transferred from one place to another, persist is not really the right verb for what they are doing.

Wim Leers’s picture

  1. 👍✅
  2. 🦅👁✅(just a typo)
  3. 🦅👁✅ (oversight in porting, dropped "either")
  4. Woah! You truly are the eagle-eyed reviewer! I have not seen you pay this much attention to detail before, in the Entity Embed issue queue, but I'm super grateful you're doing this! And better yet, you're helping me improve my English at the same time! 🙏🥳✅

Keeping RTBC because all of the changes are in comments.

Wim Leers’s picture

Now that this is RTBC, I want to make sure all people who ever contributed are credited. People who contributed to Entity Embed's src/Plugin/Filter/EntityEmbedFilter.php, which is the spiritual predecessor:

Berdir
cs_shadow
Dave Reid
deepakkumar14
Denchev
dpi
gngn
jibran
marcoscano
oknate
phenaproxima
samuel.mortenson
slashrsm
stevector
webflo
Wim Leers

phenaproxima credited dpi.

phenaproxima credited gngn.

phenaproxima’s picture

Let's see if I can assign those credits. (Turns out "Denchev" is now "thenchenv", for the record.)

phenaproxima’s picture

Crediting @nathandentzau for his patch in #8.

phenaproxima’s picture

Crediting @vijaycs85 for manual testing in #19 and patch in #20.

effulgentsia’s picture

effulgentsia’s picture

Crediting @andrewmacpherson for #40.

  • effulgentsia committed 8673640 on 8.8.x
    Issue #2940029 by Wim Leers, krlucas, legovaer, vijaycs85, nathandentzau...
effulgentsia’s picture

Status: Reviewed & tested by the community » Fixed

Thank you for everyone's great work on the Entity Embed project and in this core issue! Pushed to 8.8.x! See you in the next step in #2994696: Render embedded media items in CKEditor.

Wim Leers’s picture

Yay! Rerolled patch at #2994696-24: Render embedded media items in CKEditor, awaiting reviews from everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

oknate’s picture

redacted

xjm’s picture

Issue tags: +8.8.0 highlights

Tagging for potential mention in the release highlights as one of the many new features offered by the stable Media Library.

xjm’s picture

Status: Closed (fixed) » Needs work
Issue tags: +Needs change record

Also, as a significant API addition, this perhaps merits a CR? (Not the release notes though; there's no disruption.)

Wim Leers’s picture

Status: Needs work » Fixed
Issue tags: -Needs change record

There's no API, this is just product-level functionality. Unless we consider all filters "APIs", which would be news to me 😅

Agreed about change record, created one: https://www.drupal.org/node/3087775.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

azinck’s picture

Regarding #46, which makes this comment:

"consumed" attributes should be removed, just like \Drupal\filter\Plugin\Filter\FilterAlign and \Drupal\filter\Plugin\Filter\FilterCaption do.

Does anyone know *why* these are removed? I can't see that they do any harm, and preserving them provides very useful context for downstream filters, should they want to act on them. For instance, I'd like my media templates to be aware that there was a caption on this entity. I know that, for my specific use-case, I should be asking about why the Caption filter removes its attribute, but the question is basically the same. Anyone have any thoughts?