From 80f2ff71dc27cf89d8d1741a24f4c9621c01f99d Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Thu, 23 Jan 2025 17:12:16 +0000
Subject: [PATCH] Issue #3489179 by godotislate, ghost of drupal past:
 Referring the same entity multiple times breaks _referringItem

(cherry picked from commit 40c1ab5260fa15ae6b0ab607d175025436a241ec)
---
 .../EntityReferenceFormatterBase.php          |  9 ++++-
 .../EntityReferenceFormatterTest.php          | 38 +++++++++++++++++++
 .../FieldFormatter/ImageFormatterBase.php     |  7 ++++
 .../ImageFieldDefaultImagesTest.php           | 21 ++++++++++
 4 files changed, 74 insertions(+), 1 deletion(-)

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 32bb0c0c4cb8..5e91daf6e494 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 c2e0d85f156c..9d4449cc6829 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 f52fc1d783b6..52057fb331fd 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 7036a0b7a358..a6f9bc57c561 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.
-- 
GitLab