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
Import the code from #2801307: [META] Support WYSIWYG embedding of media entities, iterate on it, review it until it is nice, and commit it.Stabilize https://www.drupal.org/project/entity_embed, port over all test coverage, to ensure a maximally stable new filter in core.- 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:
Comment | File | Size | Author |
---|---|---|---|
#104 | interdiff.txt | 7.39 KB | Wim Leers |
#104 | 2940029-104.patch | 68.43 KB | Wim Leers |
#99 | 2940029-99.patch | 68.11 KB | Wim Leers |
#99 | interdiff.txt | 3.08 KB | Wim Leers |
#97 | 2940029-97.patch | 67.94 KB | Wim Leers |
Comments
Comment #2
phenaproximaComment #3
krlucas CreditAttribution: krlucas commentedComment #4
krlucas CreditAttribution: krlucas commentedHere's the input filter only patch.
Comment #5
krlucas CreditAttribution: krlucas commentedComment #6
phenaproximaThank 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.
Comment #7
nathandentzauComment #8
nathandentzauHey 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.
Comment #9
krlucas CreditAttribution: krlucas commented@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.
Comment #10
phenaproximaI'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.
Comment #11
krlucas CreditAttribution: krlucas commentedUpdated 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.
Comment #13
krlucas CreditAttribution: krlucas commentedIt 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.
Comment #15
phenaproximaComment #16
phenaproximaComment #17
legovaerThe 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.
Comment #18
legovaerComment #19
vijaycs85Tested manually and here are some findings:
$id is undefined.
there is no data-view-mode, it's data-entity-embed-display now?
1. Fixed #1 and #2
2. Updated issue summary with testing steps.
Comment #20
vijaycs85Comment #21
vijaycs85Comment #22
BerdirI'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).
Comment #23
Wim LeersI think that'd be #2927867: Why is my embedded image being wrapped in an <article> tag?.
Comment #24
Wim LeersI 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:
\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: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.
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 thedata-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 withdata-caption
. So this is at least one place where my initial gut feeling in the previous point seems to be confirmed.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.
Comment #25
phenaproximaDiscussed 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:
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.drupal-media
tags as placeholders for the embedded entities, rather than thedrupal-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 thedrupal-entity
tag and commit with that.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.Comment #26
AaronMcHaleSounds 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.
Comment #27
Wim LeersWe'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:
\Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter
filter, to work only with media entities (see #25, first bullet) and view modes (see #25, second bullet)<drupal-media>
instead of<drupal-entity>
(see #25, third bullet)data-align
anddata-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)\Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest
further(#25, fourth bullet needs to be done in a follow-up, that's solely adding configuration.)
To test:
<drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption>
to the allowed HTML tagsand 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
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.
Comment #29
Wim LeersThis 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.
Comment #30
phenaproximaExcellent start! I love how clean and simple it is, and how much effort and thoughtfulness has been put into it.
"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?
We might want to mark the class @internal, no?
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.
s/must/should
Private method? I heartily approve!!
This seems like something that should be in the Contextual and Quick Edit modules, respectively. Not sure how we might go about that, though.
It'd be neat to make this configurable. Totally follow-up material, though.
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.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.
I find it strange that the "true" case here is $media === NULL. Can we maybe phrase this as
$build = $media ? $this->renderMedia() : $this->renderMissingMedia()
?I love that this is in its own method.
Why is $build a reference? That's inconsistent with the function signature.
Same here. Also, what does "To be serialized later" mean? Can we remove that bit?
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.
I think the description could be better.
Why is &$node a reference?
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.
Comment #31
Wim Leers#30 Thanks for the review! 🙏
final
too)<drupal-media>
to<drupal-entity>
(per #25, third point).\Drupal\filter\Plugin\Filter\FilterCaption::process()
does the same.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
.Also spotted some nits myself, fixed those too.
Comment #32
Wim LeersOf course I find additional nits just after submitting 😅
Comment #33
Wim LeersAfter 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.
Comment #34
Wim LeersWalked through it properly with @alex.pott this time, he suggested:
final
, because it's not an existing patternprivate
, because it's not an existing pattern->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.
Comment #35
claudiu.cristeaSo, 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 filterComment #37
Wim LeersAfter 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'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!
Comment #38
Wim LeersThis addresses @alex.pott's feedback from #27. Next up is test coverage, as described in #27, specifically these two bullets:
Comment #39
Wim LeersUpdating title per #25.
Comment #40
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedPatch #38:
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").
Comment #41
andrewmacpherson CreditAttribution: andrewmacpherson as a volunteer commentedComment #42
Wim LeersTime 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!
Comment #43
Wim LeersLet's start with something simple: the visual brokenness you can see in #27.5's screenshot.
Comment #45
Wim Leers❤️
StableLibraryOverrideTest
Comment #46
Wim LeersThis 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:
data-view-mode
is able to select a view mode and associated cacheability is bubbled<drupal-media>
tags are processedTo be able to pass the above tests, three changes had to be made in the filter's code:
\Drupal\filter\Plugin\Filter\FilterAlign
and\Drupal\filter\Plugin\Filter\FilterCaption
do.Comment #47
Wim Leers#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 :)
Comment #48
Wim LeersSo 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.Comment #49
Wim LeersThe second of those three: translations. Again, with test coverage of course.
Comment #50
Wim LeersAnd last but not least: recursion protection.
Comment #51
Wim LeersConsidering 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 .
Also revamped the issue summary.
Comment #57
Wim LeersD'oh. Something silly ruining an otherwise beautiful streak of green patches! 😞
Comment #58
Wim LeersFixing the CS violations I introduced while porting this.
Comment #59
Wim LeersNote 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.
This was done at the request of #30.2, but are we sure about that?
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.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 😊
Comment #60
phenaproximaMy 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.
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.
Unless it's really difficult, I'd prefer if it were injected.
Comment #64
Wim LeersFixed 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.
Done.
Comment #65
Wim LeersSure.
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 :)
Comment #66
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThis patch is looking really good!
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.
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.Is there a reason we're not using
'#theme' => 'image'
here?If the tag name is
drupal-media
, why do we require (or even allow) thedata-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 ofEntityViewBuilderInterface
)?We're applying the same
alt
andtitle
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.Comment #67
BerdirRe 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
Comment #68
Wim Leers#66: Thanks 😊 And yay! 🥳
\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 :)Added the declarative property, removed
::disableQuickEdit()
, updatedquickedit_entity_view_alter()
.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 ondata-entity-type
anddata-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:full
onEntityViewBuilderInterface::view()
disappears.full
is a lie forNode
entities: if you loadfull
, you'll end up withdefault
.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.Comment #69
effulgentsia CreditAttribution: effulgentsia at Acquia commentedWould 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.Comment #70
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI 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.Comment #71
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPer #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?
Comment #72
effulgentsia CreditAttribution: effulgentsia at Acquia commentedIs 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?
Comment #73
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThat 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.
Comment #74
Berdir> 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.
Comment #75
effulgentsia CreditAttribution: effulgentsia at Acquia commentedHm, 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 adefault_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 addingdata-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.Comment #76
BerdirI 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.
Comment #77
dww+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
Comment #78
phenaproximaPartial review...
Doesn't this mean we need to make Filter an explicit dependency of Media?
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.
Shouldn't this be $form_state->getValue($filter_html_settings_path)?
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.
🤔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. :)
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?
Should we do this before building the render array, and return an empty array early?
Why is the library attached under a special :media_embed key? Not saying it's wrong, but maybe a comment would be helpful. :)
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.
Does this need to be static?
Comment #79
AaronMcHaleBig +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.
Comment #80
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSee #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.
Comment #81
Wim Leers#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. WRTdata-align
& friends: like I wrote in #65: . For now, omitted those.Related to that: we could make
data-align
required if thefilter_align
filter is enabled, and do the same fordata-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
oreditor_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 or view mode (the two defaults core ships with)? And even if we add an view mode by default, when does it make sense to select one of those other two?
I totally see how a and 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:
\Drupal\media\Plugin\Filter\MediaEmbed
. Put differently: it's only ever used if thefilter
module uses the@filter
plugin we provide, and in that case thefilter/caption
asset library will also exist. Very sharp observation though! 👍allowed_html
) forfilter_html
, but whether it's turned on or off.\Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testAccessUnpublished()
, observe it fail.)Also renamed
MediaFilterConfigurationUiTest
toMediaEmbedFilterConfigurationUiTest
.#79: Cool, that sounds like another vote for my proposal in #73. Looking forward to your thoughts on my proposal too 😊
#80: +1
Comment #82
phenaproximaAnother 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.
Can we be certain that $filters[$filter_name] will exist? Should we do something if it doesn't?
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 :)
Supernit: should be "media".
This comment should just say "A media entity to render." The view mode is specified by the $view_mode parameter.
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.
I think this error message should mention that we were trying to render the media item as an embed.
Same here?
I didn't know it was possible to specify function-specific coverage this way. TIL :)
Hmmm...is there precedent for this elsewhere in core? If not, I'd prefer to leave it out and not bother with
@covers
annotations.This should be $page->pressButton('Save configuration').
Same here.
I don't see any test cases for when the filter weights are incorrect...?
Comment #83
phenaproximaNit: we don't need the FQCN here, since the
@coversDefaultClass
in the class doc comment provides that already.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.I think this assertion is superfluous; it's covered by the next one.
This isn't technically needed, but +1 for explicitness!
Nit: I don't think this comment adds much. :)
I don't see where we're testing that title is ignored if the title_field setting is turned off...?
Should we also set up a mocked logger in the container, to ensure that an error is logged in this case?
These
@covers
annotations aren't really accurate, since we're not explicitly covering either of those classes.Should we use \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED for $langcode?
Can we just say "...based on the host entity's language"?
Obrigada!
...I didn't know you could yield inside a data provider. O_O
Comment #84
Wim Leers#82:
filter.module
and are therefore always present.data-view-mode
. Removingdata-view-mode
is being discussed again ironically :)\Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTest::setUpBeforeClass()
+\Drupal\Tests\block\Kernel\BlockRebuildTest::setUpBeforeClass()
.#83:
MediaEmbedFilterTest
's passing that filtering is not failing. But still, I see your point. Done.getAttachments()
could also return attachments other thanlibrary
. 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 thelibrary
attachment type exist. Very subtle, I know :)$verification_selector
s'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 ofFilterAlign
andFilterCaption
have happened.Tomorrow: #82.12 and #83.6
Comment #85
Wim LeersAdd missing test coverage discovered in #82.12.
Comment #86
Wim LeersAdd missing test coverage discovered in #83.6.
Comment #87
Wim LeersNit: s/is not/is/
Comment #88
tstoecklerOnly 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!
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.
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.But that's not really true, is it? In particular the field part...
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.
Comment #89
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI 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 aMarkupInterface
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 aMarkupInterface
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.Comment #90
Wim Leers#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:
… 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:
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:
This is exactly what's happening! The last significant work on this happened more than four years ago.
Comment #91
Wim Leers#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 :)
Comment #92
phenaproximaTo 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.
Comment #93
Wim LeersWoah! 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:
@Wim Leers in #81:
Assigning to @effulgentsia.
Comment #94
phenaproximaIn 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:
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!
Comment #95
bkosborneI 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).
Comment #96
effulgentsia CreditAttribution: effulgentsia at Acquia commentedFor me, the key for this issue is that we should not require the
data-view-mode
attribute. In other words, these code hunks: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?
Comment #97
Wim LeersI actually provided those reasons in #68. I'll repeat my answer here:
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 formedia
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 themedia_embed
filter is enabled:I personally still think we should add an
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 .So IMHO next steps are:
full
as the default, then we can just remove .Comment #99
Wim LeersFailing due to
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.)
Comment #100
Wim LeersYay, #99 is green again! Rerolled all subsequent patches too:
Comment #101
phenaproximaThe 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.
Comment #102
phenaproximaFollow-up added: #3066802: Ensure that embedded image media items do not display at unreasonable sizes by default
Comment #103
oknateExcellent 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:
to keys like this:
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' .
3) in MediaEmbedFilterTranslationTest::testTranslationSelection(), the help text is a bit confusing due to the use of the word "either":
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.
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.
Comment #104
Wim LeersKeeping RTBC because all of the changes are in comments.
Comment #105
Wim LeersNow 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:Comment #118
phenaproximaLet's see if I can assign those credits. (Turns out "Denchev" is now "thenchenv", for the record.)
Comment #119
phenaproximaCrediting @nathandentzau for his patch in #8.
Comment #120
phenaproximaCrediting @vijaycs85 for manual testing in #19 and patch in #20.
Comment #121
effulgentsia CreditAttribution: effulgentsia at Acquia commentedRe #90: #3067116: text_summary() returns malformed (not normalized) HTML for basic_html and other formats that use filter_html instead of filter_htmlcorrector
Comment #122
effulgentsia CreditAttribution: effulgentsia at Acquia commentedNote that #90 encountered a different problem than #89. Here's the issue for #89: #3067124: text_summary() returns a plain string, even if passed a MarkupInterface object.
Comment #123
effulgentsia CreditAttribution: effulgentsia at Acquia commentedCrediting @andrewmacpherson for #40.
Comment #125
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThank 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.
Comment #126
Wim LeersYay! Rerolled patch at #2994696-24: Render embedded media items in CKEditor, awaiting reviews from everyone!
Comment #128
oknateredacted
Comment #129
xjmTagging for potential mention in the release highlights as one of the many new features offered by the stable Media Library.
Comment #130
xjmAlso, as a significant API addition, this perhaps merits a CR? (Not the release notes though; there's no disruption.)
Comment #131
Wim LeersThere'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.
Comment #133
azinck CreditAttribution: azinck at Forum One commentedRegarding #46, which makes this comment:
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?