Problem/Motivation
The Media system will use the media source plugin to extract metadata from the source field, and if there is a mapping configured in the media type, will then save these values into the mapped Drupal fields. This will happen on every media entity save, and the metadata from the source plugin will be stored in the fields when either:
1- the mapped field is empty, or
2- the mapped field is not empty but the source field changed.
This logic is currently in Media::prepareSave():
...
foreach ($translation->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) {
// Only save value in the entity if the field is empty or if the
// source field changed.
if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->hasSourceFieldChanged())) {
$translation->set($entity_field_name, $media_source->getMetadata($translation, $metadata_attribute_name));
}
}
...
However, there are scenarios when Media::hasSourceFieldChanged() will return TRUE but we don't want to overwrite existing values. For example, if a media entity is created by code with legitimate (non-metadata) values in mapped fields but has the source field empty, when a new save on this entity occurs (populating the source field), the helper method will indicate that "source field has changed" (since we'll be comparing NULL with something non-NULL), and the current ::prepareSave() code will overwrite the mapped fields with whatever metadata comes from the plugin (even if these are empty).
An example of this in practice is when using the Entity Share contrib module to copy entities between Drupal installations. Entity Share will save an imported media entity twice in the same request: First, when processing the scalar values (so its code can have an ID for the entity being imported), and then a second time, when processing all referenced entities. This reflects in a bug that has the form of "imported media items come with wrong values in mapped fields".
Another use-case where this has been seen was reported in #2999370-52: Mapped media fields are overridden with metadata on translation save. However, this is different than #2999370: Mapped media fields are overridden with metadata on translation save because it doesn't relate to translations in any way.
Regardless of the architecture of Entity Share, or how edge-case these scenarios may look, I do believe there is room for improvement in core, the reasoning being:
When a media item has no value for its source field, we can't tell that an existing field value has come from a metadata attribute, so our assumption that "it should change when the source changes" is no longer valid. A better assumption IMO would be that "the mapped value should change when the source changes from a non-empty value into another non-empty value".
Steps to reproduce
- Install standard profile, enable Media
- Navigate to `/admin/structure/media/manage/image/fields` and create a new field (for example "Caption")
- Navigate to `/admin/structure/media/manage/image` and set a field mapping. For example mapping "MIME type" to the "Caption" field created above. (A more realistic test could be done by using a module such Media Image Metadata to actually map embedded captions into this field)
- Have some custom code perform these two operations in the same request:
1- Create a new image media entity, without an image file, with a value in the "Caption" field. Save the entity.
2- Assign an image file into the media item source field, and save the entity again.
Expected:
The value for the "Caption" field remains as defined in step "1" above.
Actual:
The value for the "Caption" field is overwritten with the value that comes from the source plugin for the mapped metadata property (in this example the MIME type).
Proposed resolution
Only overwrite existing values on mapped media fields when the source field changes from a non-empty value into a different non-empty value.
Remaining tasks
- Create MR
- Review
- Commit
User interface changes
None
Introduced terminology
None
API changes
\Drupal\media\Entity\Media::prepareSave() will no longer overwrite mapped field values if $media_source->getSourceFieldValue($media->getOriginal()) is empty.
Data model changes
None
Release notes snippet
N/A
Issue fork drupal-3567230
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
marcoscanoComment #6
marcoscanoCreated a new MR up-to-date with
main, the previous had been created from a wrong starting point.Comment #7
smustgrave commentedComment #8
smustgrave commentedSummary seems to have removed the steps to reproduce section. It's important to keep that section for bug please
Comment #9
marcoscanoWrote steps to reproduce in the IS. 👍
Comment #10
smustgrave commentedHave had this one open for a week finally had time.
Test coverage seems to be present https://git.drupalcode.org/issue/drupal-3567230/-/jobs/9254470
Seems to be well commented.
I don't have any additional feedback.
Comment #11
anybodyWould be great to get this merged indeed. Once finished we should also try to get #2999370: Mapped media fields are overridden with metadata on translation save fixed. Both are highly relevant on larger (multilingual) projects!
Comment #12
alexpottI think the use of empty() is suspect as 0 would be considered an empty value... but I don't think it should be.
Comment #13
anybody@alexpott thank you for the progress here! I'm just a bit unsure about the case of empty fields. If we're sure that the empty field strng is always being converted to NULL, if empty, then the NULL check is definitely correct and much better!
Then I'd definitely commit your suggestion!
Comment #14
alexpottHere's the code...
empty items definitely return NULL. And isEmpty() is called on all the items. It's up to each field to define what emptiness is. So for example and string field does
Whereas numeric items do
So '0' is not an empty value but an empty string would be.
Comment #15
anybodyThanks, 100% clear now! Applied suggestion. Ready for final review.
Comment #16
smustgrave commentedBelieve feedback for this one has been addressed
Comment #17
alexpottCommitted and pushed 57d2fe40642 to main and bcc06f69b0f to 11.x and a0601071a0e to 11.3.x. Thanks!
Comment #22
anybodyThank you all and especially @alexpott!! Now that this one is fixed, we should try getting #2999370: Mapped media fields are overridden with metadata on translation save also fixed!