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 c2f23cf..a1f74dd 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -29,6 +29,13 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase implements ContainerFactoryPluginInterface { /** + * The number of times this formatter allows rendering the same entity. + * + * @var int + */ + public static $recursiveRenderLimit = 20; + + /** * The logger factory. * * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface @@ -36,6 +43,13 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase implem protected $loggerFactory; /** + * Counter for the recursive rendering protection. + * + * @var array + */ + protected static $recursiveRenderDepth = []; + + /** * Constructs a StringFormatter instance. * * @param string $plugin_id @@ -122,15 +136,27 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { - // Protect ourselves from recursive rendering. - static $depth = 0; - $depth++; - if ($depth > 20) { - $this->loggerFactory->get('entity')->error('Recursive rendering detected when rendering entity @entity_type @entity_id. Aborting rendering.', array('@entity_type' => $entity->getEntityTypeId(), '@entity_id' => $entity->id())); - return $elements; - } - if ($entity->id()) { + // Generate a string that contains 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() + . $entity->id(); + + // Protect ourselves from recursive rendering. + if (isset(static::$recursiveRenderDepth[$recursive_render_id])) { + static::$recursiveRenderDepth[$recursive_render_id]++; + } + else { + static::$recursiveRenderDepth[$recursive_render_id] = 1; + } + + if (static::$recursiveRenderDepth[$recursive_render_id] > static::$recursiveRenderLimit) { + $this->loggerFactory->get('entity')->error('Recursive rendering detected when rendering entity @entity_type @entity_id. Aborting rendering.', array('@entity_type' => $entity->getEntityTypeId(), '@entity_id' => $entity->id())); + return $elements; + } + $elements[$delta] = entity_view($entity, $view_mode, $entity->language()->getId()); // Add a resource attribute to set the mapping property's value to the @@ -144,7 +170,6 @@ public function viewElements(FieldItemListInterface $items, $langcode) { // This is an "auto_create" item. $elements[$delta] = array('#markup' => $entity->label()); } - $depth = 0; } return $elements; diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php index cc41f7d..1aa360f 100644 --- a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php +++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php @@ -10,6 +10,7 @@ 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\field\Entity\FieldStorageConfig; use Drupal\filter\Entity\FilterFormat; use Drupal\system\Tests\Entity\EntityUnitTestBase; @@ -199,6 +200,34 @@ public function testEntityFormatter() { // Test the second field item. $renderer->renderRoot($build[1]); $this->assertEqual($build[1]['#markup'], $this->unsavedReferencedEntity->label(), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter)); + + // Test the recursive rendering protection. + $referencing_entity = entity_create($this->entityType, ['name' => $this->randomMachineName()]); + $referencing_entity->save(); + + // Create a self-reference. + $referencing_entity->{$this->fieldName}->entity = $referencing_entity; + $referencing_entity->save(); + $items = $referencing_entity->get($this->fieldName); + + // Set the default view mode to use the 'entity_reference_entity_view' + // formatter. + entity_get_display($this->entityType, $this->bundle, 'default') + ->setComponent($this->fieldName, [ + 'type' => $formatter, + ]) + ->save(); + + // Check that the recursive rendering stops after it reaches the specified + // limit. + $build = $items->view(['type' => $formatter, 'settings' => ['view_mode' => 'default']]); + $output = $renderer->renderRoot($build); + + // The entity title is printed by default so we have to multiply the + // limit by 2. + $expected_occurences = EntityReferenceEntityFormatter::$recursiveRenderLimit * 2; + $actual_occurences = substr_count($output, $referencing_entity->name->value); + $this->assertEqual($actual_occurences, $expected_occurences); } /**