core/includes/common.inc | 38 +------ .../FieldFormatter/CommentDefaultFormatter.php | 12 +++ .../EntityReferenceEntityFormatter.php | 18 ++-- .../Tests/EntityReferenceFormatterTest.php | 115 ++++++++++++++++----- .../Field/FieldFormatter/TextDefaultFormatter.php | 4 +- .../Field/FieldFormatter/TextTrimmedFormatter.php | 4 +- 6 files changed, 121 insertions(+), 70 deletions(-) diff --git a/core/includes/common.inc b/core/includes/common.inc index ea63043..8a88f66 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -3827,6 +3827,10 @@ function render(&$element) { return NULL; } if (is_array($element)) { + // Early return if this element was pre-rendered (no need to re-render). + if (isset($element['#printed']) && $element['#printed'] == TRUE && strlen($element['#markup']) > 0) { + return $element['#markup']; + } show($element); return drupal_render($element, TRUE); } @@ -4009,40 +4013,6 @@ function drupal_render_cache_generate_token() { } /** - * Pre-render callback: Renders a render cache placeholder into #markup. - * - * @param $elements - * A structured array whose keys form the arguments to l(): - * - #callback: The #post_render_cache callback that will replace the - * placeholder with its eventual markup. - * - #context: An array providing context for the #post_render_cache callback. - * - * @return - * The passed-in element containing a render cache placeholder in '#markup' - * and a callback with context, keyed by a generated unique token in - * '#post_render_cache'. - * - * @see drupal_render_cache_generate_placeholder() - */ -function drupal_pre_render_render_cache_placeholder($element) { - $callback = $element['#callback']; - if (!is_callable($callback)) { - throw new Exception(t('#callback must be a callable function.')); - } - $context = $element['#context']; - if (!is_array($context)) { - throw new Exception(t('#context must be an array.')); - } - $token = \Drupal\Component\Utility\Crypt::randomBytesBase64(55); - - // Generate placeholder markup and store #post_render_cache callback. - $element['#markup'] = drupal_render_cache_generate_placeholder($callback, $context, $token); - $element['#post_render_cache'][$callback][$token] = $context; - - return $element; -} - -/** * Processes #post_render_cache callbacks. * * #post_render_cache callbacks may modify: diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php index 154edf6..eba2654 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php @@ -145,6 +145,18 @@ public function viewElements(FieldItemListInterface $items) { if ($this->getSetting('pager_id')) { $build['pager']['#element'] = $this->getSetting('pager_id'); } + // Entity field formatters are run during the #pre_render phase of + // rendering an entity. Therefore, that is the time when this function + // runs. After that #pre_render callback, all cache tags must be + // available. However, this field formatter is highly exceptional: it + // renders *other* entities (i.e. entities within an entity). Those + // other entities come with their own #pre_render callback that must + // be called in order for the cache tags associated with them to be + // included with those of the main entity. Hence we must render the + // the other entities *immediately* (which includes running their + // #pre_render callback), otherwise their associated cache tags won't + // bubble up. + drupal_render($build, TRUE); $output['comments'] = $build; } } diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php index d8d9d41..a7e19f6 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -93,8 +93,6 @@ public function viewElements(FieldItemListInterface $items) { } if (!empty($item->target_id)) { - $elements[$delta] = entity_view($item->entity, $view_mode, $item->getLangcode()); - // Entity field formatters are run during the #pre_render phase of // rendering an entity. Therefore, that is the time when this function // runs. After that #pre_render callback, all cache tags must be @@ -102,15 +100,13 @@ public function viewElements(FieldItemListInterface $items) { // renders *another* entity (i.e. an entity within an entity). That // other entity comes with its own #pre_render callback that must be // called in order for the cache tags associated with that other entity - // to be included with those of the main entity. Hence we must run the - // other entity's #pre_render callbacks *immediately*, otherwise its - // associated cache tags won't bubble up correctly. - if (isset($elements[$delta]['#pre_render'])) { - foreach ($elements[$delta]['#pre_render'] as $callable) { - $elements[$delta] = call_user_func($callable, $elements[$delta]); - } - } - unset($elements[$delta]['#pre_render']); + // to be included with those of the main entity. Hence we must render + // the other entity *immediately* (which includes running its + // #pre_render callbacks), otherwise its associated cache tags won't + // bubble up. + $referenced_entity_build = entity_view($item->entity, $view_mode, $item->getLangcode()); + drupal_render($referenced_entity_build, TRUE); + $elements[$delta] = $referenced_entity_build; if (empty($links) && isset($result[$delta][$target_type][$item->target_id]['links'])) { // Hide the element links. diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php index 210bed4..478c506 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php @@ -9,8 +9,6 @@ use Drupal\system\Tests\Entity\EntityUnitTestBase; -use Symfony\Component\HttpFoundation\Request; - /** * Tests Entity Reference formatters. */ @@ -38,6 +36,13 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase { protected $fieldName = 'field_test'; /** + * The entity to be referenced in this test. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $referencedEntity = NULL; + + /** * Modules to enable. * * @var array @@ -55,7 +60,41 @@ public static function getInfo() { public function setUp() { parent::setUp(); + $this->installConfig(array('filter')); + entity_reference_create_instance($this->entityType, $this->bundle, $this->fieldName, 'Field test', $this->entityType); + + // Set up a field, so that the entity that'll be referenced bubbles up a + // cache tag when rendering it entirely. + entity_create('field_config', array( + 'name' => 'body', + 'entity_type' => $this->entityType, + 'type' => 'text', + 'settings' => array(), + ))->save(); + entity_create('field_instance_config', array( + 'entity_type' => $this->entityType, + 'bundle' => $this->bundle, + 'field_name' => 'body', + 'label' => 'Body', + 'settings' => array( + 'text_processing' => TRUE, + ), + ))->save(); + entity_get_display($this->entityType, $this->bundle, 'default') + ->setComponent('body', array( + 'type' => 'text_default', + 'settings' => array(), + )) + ->save(); + + // Create the entity to be referenced. + $this->referencedEntity = entity_create($this->entityType, array('name' => $this->randomName())); + $this->referencedEntity->body = array( + 'value' => '

Hello, world!

', + 'format' => 'full_html', + ); + $this->referencedEntity->save(); } /** @@ -64,15 +103,12 @@ public function setUp() { public function testAccess() { $field_name = $this->fieldName; - $entity_1 = entity_create($this->entityType, array('name' => $this->randomName())); - $entity_1->save(); - - $entity_2 = entity_create($this->entityType, array('name' => $this->randomName())); - $entity_2->save(); - $entity_2->{$field_name}->entity = $entity_1; + $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName())); + $referencing_entity->save(); + $referencing_entity->{$field_name}->entity = $this->referencedEntity; // Assert user doesn't have access to the entity. - $this->assertFalse($entity_1->access('view'), 'Current user does not have access to view the referenced entity.'); + $this->assertFalse($this->referencedEntity->access('view'), 'Current user does not have access to view the referenced entity.'); $formatter_manager = $this->container->get('plugin.manager.field.formatter'); @@ -86,10 +122,10 @@ public function testAccess() { ->save(); // Invoke entity view. - entity_view($entity_2, 'default'); + entity_view($referencing_entity, 'default'); // Verify the un-accessible item still exists. - $this->assertEqual($entity_2->{$field_name}->value, $entity_1->id(), format_string('The un-accessible item still exists after @name formatter was executed.', array('@name' => $name))); + $this->assertEqual($referencing_entity->{$field_name}->value, $this->referencedEntity->id(), format_string('The un-accessible item still exists after @name formatter was executed.', array('@name' => $name))); } } @@ -97,25 +133,58 @@ public function testAccess() { * Tests the ID formatter. */ public function testIdFormatter() { - $field_name = $this->fieldName; $formatter = 'entity_reference_entity_id'; - - // Create the entity to be referenced. - $entity_1 = entity_create($this->entityType, array('name' => $this->randomName())); - $entity_1->save(); + $field_name = $this->fieldName; // Create the entity that will have the entity reference field. - $entity_2 = entity_create($this->entityType, array('name' => $this->randomName())); - $entity_2->save(); - $entity_2->{$field_name}->entity = $entity_1; - $entity_2->{$field_name}->access = TRUE; + $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName())); + $referencing_entity->save(); + $referencing_entity->{$field_name}->entity = $this->referencedEntity; + $referencing_entity->{$field_name}->access = TRUE; - $items = $entity_2->get($field_name); + // Build the renderable array for the entity reference field. + $items = $referencing_entity->get($field_name); + $build = $items->view(array('type' => $formatter)); + + $this->assertEqual($build[0]['#markup'], $this->referencedEntity->id(), format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter))); + $expected_cache_tags = array( + $this->entityType => array($this->referencedEntity->id()), + ); + $this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter))); + + } + + /** + * Tests the entity formatter. + */ + public function testEntityFormatter() { + $formatter = 'entity_reference_entity_view'; + $field_name = $this->fieldName; + + // Create the entity that will have the entity reference field. + $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName())); + $referencing_entity->save(); + $referencing_entity->{$field_name}->entity = $this->referencedEntity; + $referencing_entity->{$field_name}->access = TRUE; // Build the renderable array for the entity reference field. + $items = $referencing_entity->get($field_name); $build = $items->view(array('type' => $formatter)); - $this->assertEqual($build[0]['#markup'], $entity_1->id(), format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter))); - $this->assertEqual($build[0]['#cache']['tags'][$this->entityType][0], $entity_1->id(), format_string('The @formatter formatter assigned the correct cache tag entity ID.', array('@formatter' => $formatter))); + $expected_rendered_body_field = '
+
Body: 
+
+
+
+
+'; + $this->assertEqual($build[0]['#markup'], 'default | ' . $this->referencedEntity->label() . $expected_rendered_body_field, format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter))); + $expected_cache_tags = array( + $this->entityType . '_view' => TRUE, + $this->entityType => array($this->referencedEntity->id() => $this->referencedEntity->id()), + 'filter_format' => array('full_html' => 'full_html'), + ); + $this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter))); } + } diff --git a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php index 74ee6ed..5e0f80b 100644 --- a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php +++ b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php @@ -39,7 +39,9 @@ public function viewElements(FieldItemListInterface $items) { '#markup' => $item->processed, '#cache' => array( 'tags' => array( - 'filter_format' => $item->format, + 'filter_format' => array( + $item->format, + ), ), ), ); diff --git a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php index fb598c8..d4b3c34 100644 --- a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php +++ b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php @@ -83,7 +83,9 @@ public function viewElements(FieldItemListInterface $items) { '#markup' => $output, '#cache' => array( 'tags' => array( - 'filter_format' => $item->format, + 'filter_format' => array( + $item->format, + ), ), ), );