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

Command icon Show commands

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

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

Comments

marcoscano created an issue. See original summary.

marcoscano’s picture

Assigned: marcoscano » Unassigned
Status: Active » Needs review

marcoscano’s picture

Issue summary: View changes

Created a new MR up-to-date with main, the previous had been created from a wrong starting point.

smustgrave’s picture

Version: 11.3.x-dev » main
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs steps to reproduce

Summary seems to have removed the steps to reproduce section. It's important to keep that section for bug please

marcoscano’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs steps to reproduce

Wrote steps to reproduce in the IS. 👍

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Bug Smash Initiative

Have 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.

anybody’s picture

Would 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!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I think the use of empty() is suspect as 0 would be considered an empty value... but I don't think it should be.

anybody’s picture

@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!

alexpott’s picture

Here's the code...

  public function getSourceFieldValue(MediaInterface $media) {
    $source_field = $this->configuration['source_field'];
    if (empty($source_field)) {
      throw new \RuntimeException('Source field for media source is not defined.');
    }

    $items = $media->get($source_field);
    if ($items->isEmpty()) {
      return NULL;
    }

    $field_item = $items->first();
    return $field_item->{$field_item->mainPropertyName()};
  }

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

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('value')->getValue();
    return $value === NULL || $value === '';
  }

Whereas numeric items do

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('value')->getValue();

    return empty($value) && (string) $value !== '0';
  }

So '0' is not an empty value but an empty string would be.

anybody’s picture

Status: Needs work » Needs review

Thanks, 100% clear now! Applied suggestion. Ready for final review.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Believe feedback for this one has been addressed

alexpott’s picture

Version: main » 11.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 57d2fe40642 to main and bcc06f69b0f to 11.x and a0601071a0e to 11.3.x. Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • alexpott committed a0601071 on 11.3.x
    fix: #3567230 Only overwrite mapped field values on media items when...

  • alexpott committed bcc06f69 on 11.x
    fix: #3567230 Only overwrite mapped field values on media items when...

  • alexpott committed 57d2fe40 on main
    fix: #3567230 Only overwrite mapped field values on media items when...
anybody’s picture

Thank 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!

Status: Fixed » Closed (fixed)

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