Problem/Motivation
After selecting an item in the media library, the selection form is currently hard coded to insert the selected items in the media library entity reference widget. It would be great if the media library also supports other field types. A good example of this is using the media library to embed media in a WYSIWYG field (see #2801307: [META] Support WYSIWYG embedding of media entities and #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library).
Proposed resolution
Add an event to handle the selection based on the opener ID in the media library state, similar to handling the access (see #3038254: Delegate media library access to the "thing" that opened the library).
Remaining tasks
- Write patch
- Review
- Commit
User interface changes
None
API changes
Add a new event to handle the media library selection.
Data model changes
None
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #56 | interdiff-3044649-54-56.txt | 577 bytes | phenaproxima |
| #56 | 3044649-56.patch | 32.42 KB | phenaproxima |
| #54 | interdiff-3044649-52-54.txt | 2.63 KB | phenaproxima |
| #54 | 3044649-54.patch | 32.42 KB | phenaproxima |
| #52 | interdiff-3044649-49-52.txt | 11.63 KB | phenaproxima |
Comments
Comment #2
seanbSince #3038254: Delegate media library access to the "thing" that opened the library already contains a lot of ground work implementing events for the media library, I based the patch on the work done there.
Comment #3
phenaproximaI love this architecture.
I think we should differentiate that this is not actually fired when someone "selects something" in the media library -- that implies that this event is fired if the user clicks on an item in the media library to select it, which is not the case. Maybe we should rephrase some of this comment to explain that this is fired once the user has finished interacting with the media library and has finished the selection process. To wit, perhaps SELECTION_COMPLETE might be a better name for the event? Not sure...
I'm not sure we should be allowing people to completely overwrite the response here. Maybe it would be wiser to only allow event subscribers to add AJAX commands? (i.e.,
$event->getResponse()->addCommand(), and remove setResponse() entirely)This method does not throw \Exception, so we don't need the @throws. :)
Nit: We don't really need $state as its own variable here. We can just call $event->getState()->getOpenerId().
Well, this sucks, but because we are beta experimental I think we need to have the $event_dispatcher parameter default to NULL, and then throw a deprecation notice if it's not passed.
Comment #4
wim leersRebased #2's
do-not-testpatch on top of @seanB's #3038254-31: Delegate media library access to the "thing" that opened the library. Did not address anything in #3.Comment #5
wim leersTo be able to provide truly constructive feedback rooted in real-world needs, I first worked on #2998005 to see if I could make this work against D8.8 HEAD (without either this patch applied or #3038254: Delegate media library access to the "thing" that opened the library).
The answer is: yes, I could — see #2998005-15: [PP-1] Support Drupal core's Media Library. The latest iteration is a bit simpler: #2998005-24: [PP-1] Support Drupal core's Media Library. The relevant code:
entity_embed_form_views_form_media_library_widget_alter()+entity_embed_form_views_form_media_library_widget_ajax_update().It overrides the
#ajaxsubmit callback; it changes it from\Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm::updateWidget()toentity_embed_form_views_form_media_library_widget_ajax_update().While I was able to make it work, it also happened to confirm the need for infrastructure like the one being proposed here. It'd mean I won't have to have a form alter hook that detects which "opener" it is anymore and then swap out the
#ajaxsubmit callback. It'd mean I have an official, supported API, rather than having to reach into implementation details. That being said, we have code all over Drupal core and contrib relying on particular form structures. So I think it would be acceptable to not have the infrastructure this patch is adding.Why must this be an AJAX response? Isn't this limiting us unnecessarily for the future? Shouldn't this be up to the event subscriber to decide?
Note that the
CloseDialogCommandbeing always present means that for the WYSIWYG (text editor) integration, it'll have to override the pre-generatedAjaxResponsecompletely, because\Drupal\editor\Ajax\EditorDialogSaveMUST run beforeCloseDialogCommand.EDIT: never mind,
\Drupal\Core\Ajax\AjaxResponse::addCommand()has an optional$prepend = FALSEparameter; that'll allow the event subscriber for the WYSIWYG (text editor) integration to prepend its\Drupal\editor\Ajax\EditorDialogSavecommand 👍Comment #6
wim leersI didn't want to end at #5, I wanted to 100% vet it. So I went ahead and created an alternative
#2998005 patch that builds on top of #3038254-31: Delegate media library access to the "thing" that opened the library and #3044649-4: Delegate media library selection handling to the "thing" that opened the library.
It'd work beautifully, except that we would unfortunately still need
entity_embed_form_views_form_media_library_widget_alter()to attach theeditor/drupal.editor.dialogasset library. That's the sole place where we need to go beyond the infrastructure that #3038254 and #3044649 (this issue) provide. That asset library is necessary to makeEditorDialogSaveAJAX commands work.See #2998005-28: [PP-1] Support Drupal core's Media Library.
Comment #7
seanbGood point. We designed this to work in a modal and communicate with the opener via JS/AJAX, but I guess there is no technical reason why the media library can't be opened via a link and return a "whatever" response when the user is done. I guess this also means the closeDialog command needs to move to the opener implementation which would address the other point you raised.
Comment #8
wim leers+1 to both; although the
CloseDialogCommandremark is kinda moot.Actually … the fact that the dialog needs to be closed is a valid reason for it to be required to use an
AjaxResponse. So it might be better to leave it as-is :)Comment #9
phenaproximaI discussed this with @effulgentsia today.
He thinks (and I agree) that adding entity-specific stuff to MediaLibraryState, as we are doing in #3038254: Delegate media library access to the "thing" that opened the library, is a strange code smell. It's strange enough that it will likely hold up that issue for a bit.
However, the current patch in this issue doesn't seem to need any entity-specific stuff at all. Which means that this patch does not need to be blocked by that patch.
So, let's flip the order. Let's add the event subscriber-based architecture in this issue and commit it first, then reroll #3038254: Delegate media library access to the "thing" that opened the library on top of that work. I validated this idea with @effulgentsia and he is for it. So let's get it done!
Comment #10
wim leersCan you explain that concern some more? Is the concern that this prevents non-entity things from using the Media Library selection UI?
P.S.: why is that on this issue instead of on #3038254: Delegate media library access to the "thing" that opened the library?
Comment #11
seanbAdded a reply to #9 in #3038254-35: Delegate media library access to the "thing" that opened the library.
Comment #12
seanbMoved the event subscriber-based architecture to this issue to not be blocked in #3038254: Delegate media library access to the "thing" that opened the library. Interdiff didn't really make sense so best to look at this from scratch.
Comment #13
phenaproximaNice work!
I don't think we need to allow people to override the response entirely. Calling code should be able to just do $event->getResponse()->addCommand().
I think we are going to need to default the $event_dispatcher to NULL and default it to \Drupal::service('event_dispatcher') (and trigger a deprecation notice) if it's not passed.
Since the $event_dispatcher parameter is new, it should be the last one, and default to NULL.
Same here.
I'm not sure if we really need these protected static members. They're completely internal implementation details of public methods, so maybe we should just hard-code them.
This comment should not mention access checking, since we're not adding that in this patch. :)
Why do we need the @see?
Although I do realize this patch has lots of implicit test coverage (all Media Library tests would break if this did not work), I think we should add some explicit test coverage as well, just to prove that the events are properly dispatched and handled. (I think a basic kernel test, with an accompanying test module, oughta do the trick.)
Comment #14
seanbFixed #13.
While writing the kernel test I came to the conclusion there is very little we can actually test for the event. We basically can only test the event exists and that it is dispatched. Maybe if it returns an AJAX response? All these things do not really add any value imho.
We already have test coverage for the media library that proves the
MediaLibraryWidgetSubscriberevent works since that code is now responsible for updating the field values on selection.If there are more tests needed please let me know what we actually want to assert, I would be happy to add it.
Comment #15
phenaproximas/property//
Apart from doc comment updates I have asked @seanB for in person, I think I'm out of things to complain about.
Comment #16
seanbFixed #15.
Comment #17
phenaproximaLooks great! I'd like a second opinion from Wim before we officially greenify this, but it's RTBC from my perspective.
Comment #19
phenaproximaI'm tagging this for backport to 8.7.x. I'm not sure this is technically a feature request; it is really just refactoring internal Media Library mechanisms. And although it's not a bugfix, it would allow Entity Embed to integrate with Media Library before 8.8, which is advantageous since we are intending to move parts of that module (including its integration with Media Library) into core. (See #2998005: [PP-1] Support Drupal core's Media Library).
Comment #20
seanbMissed one last thing. Sorry about that..
Comment #22
phenaproximaLooks like it needs a reroll =\
Comment #23
seanbRerolled.
Comment #24
phenaproximaRemoving the "Needs reroll" tag.
Comment #26
seanbThis should fix it. Not sure how I missed that before.
Comment #27
wim leerss/Access/Selection/ :)
s/CKEditor/a text editor/
Inconsistent.
No exception is thrown; should be removed.
Nit: s/comma separated/comma-separated/
Nit: typehint inaccurate.
Noting that @phenaproxima asked about this in #3038254-23: Delegate media library access to the "thing" that opened the library.6 and @seanB answered this in the next comment.
Comment #28
wim leersRebased #2998005 on top of #27, and #2998005-41: [PP-1] Support Drupal core's Media Library still works perfectly fine 👍 I wanted that extra piece of validation before RTBC'ing :)
Comment #29
alexpottI'm going to talk to @Wim Leers, @seanB and @phenaproxima about this issue at devdays cluj because I have some concerns about the architecture. I'm not sure that an event is really the right way to go but we'll hash that out and I'll make a further comment here about the resulting discussion.
Comment #30
wim leers@alexpott expressed concerns about using events for solving this issue, since the expectation for events is that multiple subscribers can chime in. In this case, only a single thing is supposed to have a say in this.
Creating a new plugin type seems overkill, tagged services are also overkill. What seems appropriate in this case is to let the specific media library opener nominate a particular service to generate a response upon selection (and the same service could also handle access checking, see #3038254: Delegate media library access to the "thing" that opened the library).
@seanB, @phenaproxima and I pair-programmed this together at Drupal Dev Days.
Comment #31
phenaproximaNitpicks!
I think we should rename this to media_library.opener.field_widget.
Nit: I don't think this should be type hinted with
|null, or marked optional. It's not. It's only optional for backwards compatibility reasons, and the parameter documentation should reflect the intention.Nit: This can all be one long chained call.
Same complaint as above.
Ditto.
Might want a better method? Also, we should add a @see comment referring to the access delegation issue.
This should probably be \InvalidArgumentException, and needs a message.
Nit: This can be one chained call.
Comment #32
wim leers#31:
Comment #34
alexpottRe #32.2 core is pretty confused on constructor argument change. @Berdir and I have talked about writing a docs page / policy issue for a while but we've never got round to it. I agree with @phenaproxima here let's document the correct usage not the deprecated usage.
Comment #35
phenaproximaRegarding hashing: let's add two new methods to MediaLibraryState: getOpenerParameter() and setOpenerParameter(). When invoked, they get or set values in a media_library_opener_parameters sub-array, which could be automatically serialized and included in the output of getHash(), and would be represented this way in the generated query strings:
/path/to/page?media_library_opener_id=whatever&media_library_opener_parameters[foo]=bar&media_library_parameters[baz]=wambooliThis would allow openers to easily get and set parameters which should be tamper-proofed by the hash.
Comment #37
phenaproximaHere's an implementation of that idea. We'll need some tests to ensure that the new methods work, and that any values therein are taken into account by getHash().
Comment #39
phenaproximaThis fixes MediaLibraryTest on my machine. I fixed some incorrect method calls, and made sure the opener parameters are handled in a consistent way. Still needs explicit tests, though.
Comment #40
phenaproximaImproved documentation on MediaLibraryState.
Comment #41
phenaproximaA few more small documentation changes, and added explicit tests of the opener parameter methods on MediaLibraryState. I don't think the explicit test coverage needs to be particularly extensive; with the changes we're making in this issue, the functional tests will break hard if the opener parameters are buggy.
Comment #42
phenaproximaSo, in re-reading #41, I realized that setOpenerParameter() is not necessary. Since MediaLibraryState::create() can take an array of opener-specific parameters, there is no need to introduce this additional mutability. So I removed it :)
Comment #43
seanbI have some small nits, but in general I think this looks good.
This is not optional right?
Now that we changed the opener to be a service, I thought about renaming the opener ID to something like service ID. After talking to phenaproxima, we decided that since these services are kind of special, the "opener" concept is probably something we should keep to help us describe/explain how this works.
Do we really need both?
I don't think this is used?
Comment #44
wim leers#34: 👍
Patch review:
Nit: per #34,
(optional)and|nullshould be removed.Nit: "handle" is pretty vague. Could be more specific.
"special" sounds … scarily artsy. We want precision. So let's change it to "additional".
Nit: do we have non-container services? 😛 Let's omit "container" here.EDIT: oh wow we apparently do this all over core. Weird. Never mind then!
This wording really confused me. Clarified.
This seems like out-of-scope refactoring, but it's both minimal and makes things simpler, so I'm fine with keeping this in this patch.
So, basically only nits. Thanks for getting it to green.
Comment #45
phenaproximaThanks all!
All feedback is fixed. With regard to #43.3, the answer is no, we don't need both; there is a hard technical reason why we need the plural getOpenerParameters() in \Drupal\media_library\MediaLibraryUiBuilder::buildMediaTypeMenu(), but the singular getOpenerParameter() is a convenience method. So I removed that with @seanB's blessing.
Comment #46
wim leers9 files changed, 278 insertions, 56 deletions.11 files changed, 225 insertions, 102 deletions.So:
Comment #47
alexpottAdd an @see to the MediaLibraryState
If the NULL ness is true that we need
= NULLon the method.It's odd that the state argument is in different places.
Should we more explicit about interfaces. I.e something like
Comment #48
wim leersExcellent nits, I wish I spotted those too :)
Comment #49
phenaproximaAll feedback from #47 is addressed in here.
Comment #50
wim leersComment #51
alexpottDiscussed with @phenaproxima and after looking at the access patch I think we should have a very very thin service that gets an MediaLibraryOpenerInterface service from the container when given a MediaLibraryState object. That way the class resolver won't end up being injected everywhere. And we can keep the interface checking in a central location.
Comment #52
phenaproximaHere ya go! Meet
\Drupal\media_library\OpenerResolverInterfaceand its offspring.Comment #53
alexpottInstead of a service ID i'd make this take an instance of MediaLibrabryState
Comment #54
phenaproximaDone.
Comment #55
nuezComment #56
phenaproximaFixed a typo in the doc comment of MediaLibraryOpenerInterface. No need to change from RTBC, since it's a one-line change (albeit a significant one).
Comment #57
alexpottCommitted and pushed ca0ac736fd to 8.8.x and 9a654bb02a to 8.7.x. Thanks!
Backported to 8.7.x since media library is experimental.
Fixed unused use on commit.
Comment #60
wim leersYay, this means #2998005: [PP-1] Support Drupal core's Media Library can also use this!
Comment #61
rosinegrean commented