diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php index 32bb0c0c4cb85a21a68a7280575f8b16ba525e87..5e91daf6e494bef85a544bdfdeddb46f9bf9b2c0 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php @@ -57,7 +57,14 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item $item->_accessCacheability = CacheableMetadata::createFromObject($access); if ($access->isAllowed()) { // Add the referring item, in case the formatter needs it. - $entity->_referringItem = $items[$delta]; + if (isset($entity->_referringItem) && ($entity->_referringItem !== $item)) { + // If the entity is already being referenced by another field item, + // clone the entity so that _referringItem is set to the correct + // item in each instance. + $entity = clone $entity; + $item->entity = $entity; + } + $entity->_referringItem = $item; $entities[$delta] = $entity; } } diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php index c2e0d85f156cc9521fb1d4931d62e62939747bc5..9d4449cc68297e053464cf5a9442db669733ed64 100644 --- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php +++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php @@ -424,6 +424,44 @@ public function testLabelFormatter(): void { $this->assertEquals($build[0]['#plain_text'], $entity_with_user->get('user_id')->entity->label(), 'For inaccessible links, the label should be displayed in plain text.'); } + /** + * Tests formatters set the correct _referringItem on referenced entities. + */ + public function testFormatterReferencingItem(): void { + $storage = \Drupal::entityTypeManager()->getStorage($this->entityType); + // Create a referencing entity and confirm that the _referringItem property + // on the referenced entity in the built render array's items is set to the + // field item on the referencing entity. + $referencing_entity_1 = $storage->create([ + 'name' => $this->randomMachineName(), + $this->fieldName => $this->referencedEntity, + ]); + $referencing_entity_1->save(); + $build_1 = $referencing_entity_1->get($this->fieldName)->view(['type' => 'entity_reference_entity_view']); + $this->assertEquals($this->referencedEntity->id(), $build_1['#items'][0]->entity->id()); + $this->assertEquals($referencing_entity_1->id(), $build_1['#items'][0]->entity->_referringItem->getEntity()->id()); + $this->assertEquals($referencing_entity_1->id(), $build_1[0]['#' . $this->entityType]->_referringItem->getEntity()->id()); + // Create a second referencing entity and confirm that the _referringItem + // property on the referenced entity in the built render array's items is + // set to the field item on the second referencing entity. + $referencing_entity_2 = $storage->create([ + 'name' => $this->randomMachineName(), + $this->fieldName => $this->referencedEntity, + ]); + $referencing_entity_2->save(); + $build_2 = $referencing_entity_2->get($this->fieldName)->view(['type' => 'entity_reference_entity_view']); + $this->assertEquals($this->referencedEntity->id(), $build_2['#items'][0]->entity->id()); + $this->assertEquals($referencing_entity_2->id(), $build_2['#items'][0]->entity->_referringItem->getEntity()->id()); + $this->assertEquals($referencing_entity_2->id(), $build_2[0]['#' . $this->entityType]->_referringItem->getEntity()->id()); + // Confirm that the _referringItem property for the entity referenced by the + // first referencing entity is still set to the field item on the first + // referencing entity. + $this->assertEquals($referencing_entity_1->id(), $build_1['#items'][0]->entity->_referringItem->getEntity()->id()); + // Confirm that the _referringItem property is not the same for the two + // render arrays. + $this->assertNotEquals($build_1['#items'][0]->entity->_referringItem->getEntity()->id(), $build_2['#items'][0]->entity->_referringItem->getEntity()->id()); + } + /** * Sets field values and returns a render array. * diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php index f52fc1d783b67c4adc938f0ff9451f209a13aecc..52057fb331fd3c405ef0719015549fa39d1eedfc 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php @@ -38,6 +38,13 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item '_loaded' => TRUE, '_is_default' => TRUE, ]); + if ($file->_referringItem) { + // If the file entity is already being referenced by another field + // item, clone it so that _referringItem is set to the correct item + // in each instance. + $file = clone $file; + $items[0]->entity = $file; + } $file->_referringItem = $items[0]; } } diff --git a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php index 7036a0b7a358b13357f1b95dfaa890f329068185..a6f9bc57c561f5ec94ee0ced913b5ec217330d90 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php @@ -145,6 +145,27 @@ public function testDefaultImages(): void { $article = $this->drupalCreateNode(['type' => 'article']); $article_built = $this->drupalBuildEntityView($article); $this->assertEquals($default_images['field']->id(), $article_built[$field_name][0]['#item']->target_id, "A new article node without an image has the expected default image file ID of {$default_images['field']->id()}."); + // Confirm that the default image entity _referringItem property is set to + // the field item on the article node. + $article_default_image_referring_entity = $article_built[$field_name][0]['#item']->entity->_referringItem->getEntity(); + $this->assertEquals($article->id(), $article_default_image_referring_entity->id()); + + // Confirm that the image default is shown for another new article node. + $article2 = $this->drupalCreateNode(['type' => 'article']); + $article2_built = $this->drupalBuildEntityView($article2); + $this->assertEquals($default_images['field']->id(), $article2_built[$field_name][0]['#item']->target_id, "A new article node without an image has the expected default image file ID of {$default_images['field']->id()}."); + // Confirm that the default image entity _referringItem property is set to + // the field item on the second article node. + $article2_default_image_referring_entity = $article2_built[$field_name][0]['#item']->entity->_referringItem->getEntity(); + $this->assertEquals($article2->id(), $article2_default_image_referring_entity->id()); + // Confirm that the default image entity _referringItem property on the + // first article is still set to the field item on the article node. + $article_default_image_referring_entity = $article_built[$field_name][0]['#item']->entity->_referringItem->getEntity(); + $this->assertEquals($article->id(), $article_default_image_referring_entity->id()); + + // Confirm that the _referringItem values for the default image entities on + // the two nodes are referring to field items on different nodes. + $this->assertNotEquals($article_default_image_referring_entity->id(), $article2_default_image_referring_entity->id()); // Also check that the field renders without warnings when the label is // hidden.