From 8ffc9ecc6a0c8986115cc9a678b92f9aecb945a9 Mon Sep 17 00:00:00 2001
From: Ahmad Nawaz <ahmad.nawaz@paddle.be>
Date: Thu, 27 Feb 2025 18:16:01 +0500
Subject: [PATCH 1/2] Can only intentionally re-render an entity with
 references 20 times

---
 .../Drupal/Core/Entity/EntityViewBuilder.php  |  92 +++++++++++-
 .../EntityReferenceEntityFormatter.php        |  34 -----
 .../comment/src/CommentViewBuilder.php        |   4 +-
 .../EntityReferenceAutoCreateTest.php         |  50 +++++++
 .../EntityReferenceFormatterTest.php          | 138 ++++++++++++++++--
 .../media/src/Plugin/Filter/MediaEmbed.php    |  31 ----
 .../tests/src/Kernel/MediaEmbedFilterTest.php |   9 +-
 core/tests/Drupal/Tests/EntityViewTrait.php   |   9 ++
 8 files changed, 283 insertions(+), 84 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 149ea3ca32ce..8624460a403e 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -3,12 +3,14 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Random;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Field\FieldItemInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Logger\LoggerChannelTrait;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\Registry;
@@ -22,6 +24,8 @@
  */
 class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterface, EntityViewBuilderInterface, TrustedCallbackInterface {
 
+  use LoggerChannelTrait;
+
   /**
    * The type of entities for which this view builder is instantiated.
    *
@@ -80,6 +84,15 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
    */
   protected $singleFieldDisplays;
 
+  /**
+   * A collection of keys.
+   *
+   * It identifies rendering in progress, used to prevent recursion.
+   *
+   * @var array
+   */
+  protected array $recursionKeys = [];
+
   /**
    * Constructs a new EntityViewBuilder.
    *
@@ -107,13 +120,15 @@ public function __construct(EntityTypeInterface $entity_type, EntityRepositoryIn
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
+    $instance = new static(
       $entity_type,
       $container->get('entity.repository'),
       $container->get('language_manager'),
       $container->get('theme.registry'),
       $container->get('entity_display.repository')
     );
+    $instance->setLoggerFactory($container->get('logger.factory'));
+    return $instance;
   }
 
   /**
@@ -136,7 +151,12 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
    * {@inheritdoc}
    */
   public static function trustedCallbacks() {
-    return ['build', 'buildMultiple'];
+    return [
+      'build',
+      'buildMultiple',
+      'setRecursiveRenderProtection',
+      'unsetRecursiveRenderProtection',
+    ];
   }
 
   /**
@@ -189,6 +209,9 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
         'max-age' => $entity->getCacheMaxAge(),
       ],
     ];
+    // Add callbacks to protect from recursive rendering.
+    $build['#pre_render'] = [[$this, 'setRecursiveRenderProtection']];
+    $build['#post_render'] = [[$this, 'unsetRecursiveRenderProtection']];
 
     // Add the default #theme key if a template exists for it.
     if ($this->themeRegistry->getRuntime()->has($this->entityTypeId)) {
@@ -535,4 +558,69 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
     return $display;
   }
 
+  /**
+   * Entity render array #pre_render callback.
+   */
+  public function setRecursiveRenderProtection(array $build): array {
+    // Checks whether entity render array with matching cache keys is being
+    // recursively rendered. If not already being rendered, add an entry to track
+    // that it is.
+    $recursion_key = $this->getRenderRecursionKey($build);
+    if ((count($this->recursionKeys) !== 1) && isset($this->recursionKeys[$recursion_key])) {
+      $this->getLogger('entity')
+        ->error('Recursive rendering attempt aborted for %key. In progress: %guards', [
+          '%key' => $recursion_key,
+          '%guards' => print_r($this->recursionKeys, TRUE),
+        ]);
+      $build['#printed'] = TRUE;
+    }
+    else {
+      $this->recursionKeys[$recursion_key] = $recursion_key;
+    }
+    return $build;
+  }
+
+  /**
+   * Entity render array #post_render callback.
+   */
+  public function unsetRecursiveRenderProtection(string $renderedEntity, array $build): string {
+    // Removes rendered entity matching cache keys from recursive render
+    // tracking, once the entity has been rendered.
+    $recursion_key = $this->getRenderRecursionKey($build);
+    unset($this->recursionKeys[$recursion_key]);
+
+    return $renderedEntity;
+  }
+
+  /**
+   * Generates a key for an entity render array for recursion protection.
+   *
+   * @param array $build
+   *   The entity render array.
+   *
+   * @return string
+   *   The key to ID the build array within recursion tracking.
+   */
+  protected function getRenderRecursionKey(array $build): string {
+    /** @var \Drupal\Core\Entity\EntityInterface $entity */
+    $entity = $build['#' . $this->entityTypeId];
+    // If entity is new and has no ID, generate a unique random string.
+    if ($entity->id()) {
+      $entity_id = $entity->id();
+    }
+    else {
+      if (!$entity->_tempRecursionRenderId) {
+        $entity->_tempRecursionRenderId = (new Random())->string(8, TRUE);
+      }
+      $entity_id = $entity->_tempRecursionRenderId;
+    }
+    // It seems very unlikely that the same entity displayed in the same view
+    // mode would be recursively nested and meant to be displayed differently,
+    // so a key made up of the entity type ID, entity ID, and view mode should
+    // suffice for recursion detection
+    return $entity->getEntityTypeId()
+      . $entity_id
+      . $build['#view_mode'];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
index b80f036ea38b..de42113ae9ab 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
@@ -160,40 +160,6 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $elements = [];
 
     foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
-      // Due to render caching and delayed calls, the viewElements() method
-      // will be called later in the rendering process through a '#pre_render'
-      // callback, so we need to generate a counter that takes into account
-      // all the relevant information about this field and the referenced
-      // entity that is being rendered.
-      $recursive_render_id = $items->getFieldDefinition()->getTargetEntityTypeId()
-        . $items->getFieldDefinition()->getTargetBundle()
-        . $items->getName()
-        // We include the referencing entity, so we can render default images
-        // without hitting recursive protections.
-        . $items->getEntity()->id()
-        . $entity->getEntityTypeId()
-        . $entity->id();
-
-      if (isset(static::$recursiveRenderDepth[$recursive_render_id])) {
-        static::$recursiveRenderDepth[$recursive_render_id]++;
-      }
-      else {
-        static::$recursiveRenderDepth[$recursive_render_id] = 1;
-      }
-
-      // Protect ourselves from recursive rendering.
-      if (static::$recursiveRenderDepth[$recursive_render_id] > static::RECURSIVE_RENDER_LIMIT) {
-        $this->loggerFactory->get('entity')->error('Recursive rendering detected when rendering entity %entity_type: %entity_id, using the %field_name field on the %parent_entity_type:%parent_bundle %parent_entity_id entity. Aborting rendering.', [
-          '%entity_type' => $entity->getEntityTypeId(),
-          '%entity_id' => $entity->id(),
-          '%field_name' => $items->getName(),
-          '%parent_entity_type' => $items->getFieldDefinition()->getTargetEntityTypeId(),
-          '%parent_bundle' => $items->getFieldDefinition()->getTargetBundle(),
-          '%parent_entity_id' => $items->getEntity()->id(),
-        ]);
-        return $elements;
-      }
-
       $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId());
       $elements[$delta] = $view_builder->view($entity, $view_mode, $entity->language()->getId());
 
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index 0e4e024dd574..b0a7af4f800e 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -61,7 +61,7 @@ public function __construct(EntityTypeInterface $entity_type, EntityRepositoryIn
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
+    $instance = new static(
       $entity_type,
       $container->get('entity.repository'),
       $container->get('language_manager'),
@@ -70,6 +70,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $container->get('entity_display.repository'),
       $container->get('entity_type.manager')
     );
+    $instance->setLoggerFactory($container->get('logger.factory'));
+    return $instance;
   }
 
   /**
diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAutoCreateTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAutoCreateTest.php
index 7a982ed1bf43..4893ec4b65ec 100644
--- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAutoCreateTest.php
+++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAutoCreateTest.php
@@ -304,4 +304,54 @@ public function testNoBundles(): void {
     $this->assertEquals($referenced_id, $referencing_node->$field_name->target_id, 'Newly created node is referenced from the referencing entity.');
   }
 
+  /**
+   * Test previewing autocreated node in a new node is not flagged as recursion.
+   *
+   * Entity render recursion is prevented by pushing/popping entities being
+   * rendered on to a stack keyed by a string made up of the entity type ID,
+   * entity ID, and the view mode. In the case of new entities, the entity ID is
+   * NULL, so this test checks that unique identifiers are used in the stack
+   * keys for new entities to prevent false positive recursion protection.
+   */
+  public function testAutoCreatedNodeNewNodePreview(): void {
+    // Set the referenced node to be displayed as a rendered entity in the same
+    // view mode as the referencing node.
+    $display_repository = \Drupal::service('entity_display.repository');
+    $display_repository->getViewDisplay('node', $this->referencingType)
+      ->setComponent('test_field', [
+        'type' => 'entity_reference_entity_view',
+        'settings' => [
+          'view_mode' => 'full',
+        ],
+      ])
+      ->save();
+    // Remove the links component from the referenced node's display because
+    // there would be errors rendering links on the referenced entity without an
+    // ID.
+    $display_repository->getViewDisplay('node', $this->referencedType)
+      ->removeComponent('links')
+      ->save();
+
+    // Assert referenced node does not exist.
+    $referenced_title = $this->randomMachineName();
+    $result = \Drupal::entityQuery('node')
+      ->accessCheck(FALSE)
+      ->condition('type', $this->referencedType)
+      ->condition('title', $referenced_title)
+      ->execute();
+    $this->assertEmpty($result, 'Referenced node does not exist yet.');
+
+    // Preview a new referencing node from the node add form.
+    $edit = [
+      'title[0][value]' => $this->randomMachineName(),
+      'test_field[0][target_id]' => $referenced_title,
+    ];
+    $this->drupalGet("node/add/{$this->referencingType}");
+    $this->submitForm($edit, 'Preview');
+    $this->assertSession()->statusCodeEquals(200);
+    // Referenced node title should appear if not blocked by recursion
+    // protection.
+    $this->assertSession()->pageTextContains($referenced_title);
+  }
+
 }
diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
index 9d4449cc6829..12da08fd6020 100644
--- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
+++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
@@ -7,7 +7,6 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
-use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -17,6 +16,9 @@
 use Drupal\user\RoleInterface;
 use Drupal\entity_test\Entity\EntityTestLabel;
 use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
 
 /**
  * Tests the formatters functionality.
@@ -234,6 +236,44 @@ public function testEntityFormatter(): void {
     $this->assertSame('default | ' . $this->unsavedReferencedEntity->label() . $expected_rendered_name_field_2 . $expected_rendered_body_field_2, (string) $build[1]['#markup'], sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
   }
 
+  /**
+   * Tests recursive rendering protection failing over single entity N times.
+   */
+  public function testEntityFormatterRecursiveRenderingFailing(): void {
+    $request = Request::create('http://example.com', 'POST');
+    $request->setSession(new Session(new MockArraySessionStorage()));
+    \Drupal::requestStack()->push($request);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $formatter = 'entity_reference_entity_view';
+    $view_builder = $this->entityTypeManager->getViewBuilder($this->entityType);
+
+    // Set the default view mode to use the 'entity_reference_entity_view'
+    // formatter.
+    \Drupal::service('entity_display.repository')
+      ->getViewDisplay($this->entityType, $this->bundle)
+      ->setComponent($this->fieldName, [
+        'type' => $formatter,
+      ])
+      ->save();
+
+    $storage = \Drupal::entityTypeManager()->getStorage($this->entityType);
+    $entity = $storage->create(['name' => $this->randomMachineName()]);
+    $entity->save();
+    $referencing_entity = $storage->create([
+      'name' => $this->randomMachineName(),
+      $this->fieldName => $entity->id(),
+    ]);
+    $referencing_entity->save();
+
+    $count = 21;
+    $build = $view_builder->viewMultiple(array_fill(0, $count, $referencing_entity), 'default');
+    $output = (string) $renderer->renderRoot($build);
+    // The title of entity_test entities is printed twice by default, so we have
+    // to multiply our count by 2.
+    $this->assertSame($count * 2, substr_count($output, $entity->name->value));
+  }
+
   /**
    * Tests the recursive rendering protection of the entity formatter.
    */
@@ -260,8 +300,22 @@ public function testEntityFormatterRecursiveRendering(): void {
     $referencing_entity_1->{$this->fieldName}->entity = $referencing_entity_1;
     $referencing_entity_1->save();
 
-    // Check that the recursive rendering stops after it reaches the specified
-    // limit.
+    // Using a different view mode is not recursion.
+    $build = $view_builder->view($referencing_entity_1, 'teaser');
+    $output = (string) $renderer->renderRoot($build);
+    // 2 occurrences of the entity title per entity.
+    $expected_occurrences = 4;
+
+    $actual_occurrences = substr_count($output, $referencing_entity_1->name->value);
+    $this->assertEquals($expected_occurrences, $actual_occurrences);
+
+    // Self-references should not be rendered.
+    // entity_1 -> entity_1
+    $build = $view_builder->view($referencing_entity_1, 'default');
+    $output = (string) $renderer->renderRoot($build);
+    $expected_occurrences = 2;
+    $actual_occurrences = substr_count($output, $referencing_entity_1->name->value);
+    $this->assertEquals($expected_occurrences, $actual_occurrences);
     $build = $view_builder->view($referencing_entity_1, 'default');
     $output = (string) $renderer->renderRoot($build);
 
@@ -270,27 +324,46 @@ public function testEntityFormatterRecursiveRendering(): void {
     // Additionally, we have to take into account 2 additional occurrences of
     // the entity title because we're rendering the full entity, not just the
     // reference field.
-    $expected_occurrences = EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT * 2 + 2;
+    // Repetition is not wrongly detected as recursion.
+    // entity_1 -> entity_1
+    $output = (string) $renderer->renderRoot($build);
     $actual_occurrences = substr_count($output, $referencing_entity_1->label());
     $this->assertEquals($expected_occurrences, $actual_occurrences);
 
-    // Repeat the process with another entity in order to check that the
-    // 'recursive_render_id' counter is generated properly.
+    // Referencing from another entity works fine.
+    // entity_2 -> entity_1
     $referencing_entity_2 = $storage->create(['name' => $this->randomMachineName()]);
     $referencing_entity_2->save();
-    $referencing_entity_2->{$this->fieldName}->entity = $referencing_entity_2;
+    $referencing_entity_2->{$this->fieldName}->entity = $referencing_entity_1;
     $referencing_entity_2->save();
 
     $build = $view_builder->view($referencing_entity_2, 'default');
     $output = (string) $renderer->renderRoot($build);
 
-    $actual_occurrences = substr_count($output, $referencing_entity_2->label());
+    $actual_occurrences = substr_count($output, $referencing_entity_1->name->value);
     $this->assertEquals($expected_occurrences, $actual_occurrences);
 
-    // Now render both entities at the same time and check again.
+    // Referencing from multiple is fine.
+    // entity_1 -> entity_1
+    // entity_2 -> entity_1
     $build = $view_builder->viewMultiple([$referencing_entity_1, $referencing_entity_2], 'default');
     $output = (string) $renderer->renderRoot($build);
 
+    // entity_1 should be seen once as a parent and once as a child of entity_2.
+    $expected_occurrences = 4;
+    // entity_2 is seen only once, as a parent.
+    $expected_occurrences = 2;
+    $actual_occurrences = substr_count($output, $referencing_entity_2->name->value);
+    $this->assertEquals($expected_occurrences, $actual_occurrences);
+    // Indirect recursion is not ok.
+    // entity_2 -> entity_1 -> entity_2
+    $referencing_entity_1->{$this->fieldName}->entity = $referencing_entity_2;
+    $referencing_entity_1->save();
+    $build = $view_builder->view($referencing_entity_2, 'default');
+    $output = (string) $renderer->renderRoot($build);
+    // Each entity should be seen once.
+    $expected_occurrences = 2;
+
     $actual_occurrences = substr_count($output, $referencing_entity_1->label());
     $this->assertEquals($expected_occurrences, $actual_occurrences);
 
@@ -341,6 +414,53 @@ public function testEntityReferenceRecursiveProtectionWithManyRenderedEntities()
     $this->assertEquals($expected_occurrences, $actual_occurrences);
   }
 
+  /**
+   * Tests multiple renderings of an entity that references another.
+   */
+  public function testEntityReferenceRecursionProtectionWithRepeatedReferencingEntity(): void {
+    $request = Request::create('http://example.com', 'POST');
+    $request->setSession(new Session(new MockArraySessionStorage()));
+    \Drupal::requestStack()->push($request);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $formatter = 'entity_reference_entity_view';
+    $view_builder = $this->entityTypeManager->getViewBuilder($this->entityType);
+
+    // Set the default view mode to use the 'entity_reference_entity_view'
+    // formatter.
+    \Drupal::service('entity_display.repository')
+      ->getViewDisplay($this->entityType, $this->bundle)
+      ->setComponent($this->fieldName, [
+        'type' => $formatter,
+      ])
+      ->save();
+
+    $storage = \Drupal::entityTypeManager()->getStorage($this->entityType);
+    $entity = $storage->create(['name' => $this->randomMachineName()]);
+    $entity->save();
+    $referencing_entity = $storage->create([
+      'name' => $this->randomMachineName(),
+      $this->fieldName => $entity->id(),
+    ]);
+    $referencing_entity->save();
+
+    // Large-scale repetition within a single render root is not recursion.
+    $count = 30;
+    $build = $view_builder->viewMultiple(array_fill(0, $count, $referencing_entity), 'default');
+    $output = (string) $renderer->renderRoot($build);
+    // The title of entity_test entities is printed twice by default, so we have
+    // to multiply our count by 2.
+    $this->assertSame($count * 2, substr_count($output, $entity->name->value));
+
+    // Large-scale repetition across render roots is not recursion.
+    for ($i = 0; $i < $count; $i++) {
+      $build = $view_builder->view($referencing_entity, 'default');
+      $output = (string) $renderer->renderRoot($build);
+      // The title of entity_test entities is printed twice by default.
+      $this->assertSame(2, substr_count($output, $entity->name->value));
+    }
+  }
+
   /**
    * Tests the label formatter.
    */
diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
index 118422bf2c28..007385db3611 100644
--- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -85,18 +85,6 @@ class MediaEmbed extends FilterBase implements ContainerFactoryPluginInterface,
    */
   protected $loggerFactory;
 
-  /**
-   * An array of counters for the recursive rendering protection.
-   *
-   * Each counter takes into account all the relevant information about the
-   * field and the referenced entity that is being rendered.
-   *
-   * @var array
-   *
-   * @see \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::$recursiveRenderDepth
-   */
-  protected static $recursiveRenderDepth = [];
-
   /**
    * Constructs a MediaEmbed object.
    *
@@ -213,25 +201,6 @@ public static function validateOptions(array &$element, FormStateInterface $form
    *   A render array.
    */
   protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
-    // Due to render caching and delayed calls, filtering happens later
-    // in the rendering process through a '#pre_render' callback, so we
-    // need to generate a counter for the media entity that is being embedded.
-    // @see \Drupal\filter\Element\ProcessedText::preRenderText()
-    $recursive_render_id = $media->uuid();
-    if (isset(static::$recursiveRenderDepth[$recursive_render_id])) {
-      static::$recursiveRenderDepth[$recursive_render_id]++;
-    }
-    else {
-      static::$recursiveRenderDepth[$recursive_render_id] = 1;
-    }
-    // Protect ourselves from recursive rendering: return an empty render array.
-    if (static::$recursiveRenderDepth[$recursive_render_id] > EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT) {
-      $this->loggerFactory->get('media')->error('During rendering of embedded media: recursive rendering detected for %entity_id. Aborting rendering.', [
-        '%entity_id' => $media->id(),
-      ]);
-      return [];
-    }
-
     $build = $this->entityTypeManager
       ->getViewBuilder('media')
       ->view($media, $view_mode, $langcode);
diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
index e283aae92bb1..0af8d0b4ffe3 100644
--- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
@@ -381,16 +381,11 @@ public function testRecursionProtection(): void {
       'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
     ]);
 
-    // Render and verify the presence of the embedded entity 20 times.
-    for ($i = 0; $i < 20; $i++) {
+    // Render and verify the presence of the embedded entity 30 times.
+    for ($i = 0; $i < 30; $i++) {
       $this->applyFilter($text);
       $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="default"]'));
     }
-
-    // Render a 21st time, this is exceeding the recursion limit. The entity
-    // embed markup will be stripped.
-    $this->applyFilter($text);
-    $this->assertEmpty($this->getRawContent());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/EntityViewTrait.php b/core/tests/Drupal/Tests/EntityViewTrait.php
index a1568119f8b6..a2a3f2d3476a 100644
--- a/core/tests/Drupal/Tests/EntityViewTrait.php
+++ b/core/tests/Drupal/Tests/EntityViewTrait.php
@@ -63,6 +63,15 @@ protected function buildEntityView(EntityInterface $entity, $view_mode = 'full',
     $build = $render_controller->view($entity, $view_mode, $langcode);
     $ensure_fully_built($build);
 
+    // EntityViewBuilder adds a #pre_render hook that tracks entity render array
+    // to prevent recursive rendering. Since the #pre_render hooks were called
+    // manually here without rendering actually taking place, unset the tracking
+    // for the render array so that when it is rendered, it does not trigger
+    // the recursive protection.
+    if (method_exists($render_controller, 'unsetRecursiveRenderProtection')) {
+      $render_controller->unsetRecursiveRenderProtection('', $build);
+    }
+
     return $build;
   }
 
-- 
GitLab


From de3fec62390707189a3b59e1b35e931ae51b276d Mon Sep 17 00:00:00 2001
From: Ahmad Nawaz <ahmad.nawaz@paddle.be>
Date: Thu, 27 Feb 2025 19:19:42 +0500
Subject: [PATCH 2/2] Removed un-used use statement

---
 core/modules/media/src/Plugin/Filter/MediaEmbed.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
index 007385db3611..426d429e6256 100644
--- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -7,7 +7,6 @@
 use Drupal\Core\Entity\EntityRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-- 
GitLab