.../EntityReferenceEntityFormatter.php | 2 +- .../EntityReferenceFormatterBase.php | 87 ++++++++++++++++------ .../FieldFormatter/EntityReferenceIdFormatter.php | 2 +- .../EntityReferenceLabelFormatter.php | 3 +- .../EntityReferenceFormatterTest.php | 13 +++- .../Field/FieldFormatter/GenericFileFormatter.php | 2 +- .../Field/FieldFormatter/RSSEnclosureFormatter.php | 2 +- .../Plugin/Field/FieldFormatter/TableFormatter.php | 2 +- .../Field/FieldFormatter/UrlPlainFormatter.php | 2 +- .../Plugin/Field/FieldFormatter/ImageFormatter.php | 2 +- .../Field/FieldFormatter/ImageFormatterBase.php | 10 +-- .../FieldFormatter/ResponsiveImageFormatter.php | 2 +- .../EntityReferenceTaxonomyTermRssFormatter.php | 2 +- 13 files changed, 85 insertions(+), 46 deletions(-) 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 6155d10..1328f65 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -123,7 +123,7 @@ public function viewElements(FieldItemListInterface $items) { $view_mode = $this->getSetting('view_mode'); $elements = array(); - foreach ($this->getEntitiesToView($elements, $items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items) as $delta => $entity) { // Protect ourselves from recursive rendering. static $depth = 0; $depth++; 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 0a5b159..35ee5ad 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php @@ -7,8 +7,8 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Render\BubbleableMetadata; @@ -33,34 +33,24 @@ * is available in $entity->_referringItem. This is useful for field types * that store additional values next to the reference itself. * - * @param array $build - * The renderable array that will contain the rendered field. Will be - * updated by reference to set cacheability metadata (#cache). * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items * The item list. * * @return \Drupal\Core\Entity\EntityInterface[] * The array of referenced entities to display, keyed by delta. + * + * @see ::prepareView() */ - protected function getEntitiesToView(array &$build, EntityReferenceFieldItemListInterface $items) { + protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) { $entities = array(); - $access_cacheability = new BubbleableMetadata(); - $parent_entity_langcode = $items->getEntity()->language()->getId(); foreach ($items as $delta => $item) { // Ignore items where no entity could be loaded in prepareView(). if (!empty($item->_loaded)) { /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $item->entity; - // Set the entity in the correct language for display. - if ($entity instanceof TranslatableInterface && $entity->hasTranslation($parent_entity_langcode)) { - $entity = $entity->getTranslation($parent_entity_langcode); - } - - $access = $this->checkAccess($item); - $access_cacheability = $access_cacheability->merge(BubbleableMetadata::createFromObject($access)); - if ($access->isAllowed()) { + if ($item->_access->isAllowed()) { // Add the referring item, in case the formatter needs it. $entity->_referringItem = $items[$delta]; $entities[$delta] = $entity; @@ -68,20 +58,58 @@ protected function getEntitiesToView(array &$build, EntityReferenceFieldItemList } } - // Apply the cacheability metadata for all referenced entities. This causes - // this field to be rendered (and cached) according to the cache contexts by - // which the access results vary, to ensure only users with access to this - // field can view it. It also tags this field with the cache tags on which - // the access results depend, to ensure users that cannot view this field at - // the moment will gain access once any of those cache tags are invalidated. - $access_cacheability->applyTo($build); - return $entities; } /** * {@inheritdoc} * + * @see ::prepareView() + */ + public function view(FieldItemListInterface $items) { + $addition = parent::view($items); + + $field_level_access_cacheability = new BubbleableMetadata(); + + // Try to map the cacheability of the access result that was set at _access + // in prepareView() to the corresponding render subtree. If no such subtree + // is found, then merge it with the field-level access cacheability. + foreach ($items as $delta => $item) { + // Ignore items where no entity could be loaded in prepareView(). + if (!empty($item->_loaded)) { + $access_cacheability = BubbleableMetadata::createFromObject($item->_access); + if ($item->_access->isAllowed()) { + if (isset($addition[$delta])) { + BubbleableMetadata::createFromRenderArray($addition[$delta]) + ->merge($access_cacheability) + ->applyTo($addition[$delta]); + } + else { + $field_level_access_cacheability = $field_level_access_cacheability->merge($access_cacheability); + } + } + else { + $field_level_access_cacheability = $field_level_access_cacheability->merge($access_cacheability); + } + } + } + + // Apply the cacheability metadata for the inaccessible entities and the + // entities for which the corresponding render subtree could not be found. + // This causes the field to be rendered (and cached) according to the cache + // contexts by which the access results vary, to ensure only uses with + // access to this field can view it. It also tags this field with the cache + // tags on which the access results depend, to ensure users that cannot view + // this field at the moment will gain access once any of those cache tags + // are invalidated. + $field_level_access_cacheability->applyTo($addition); + + return $addition; + } + + /** + * {@inheritdoc} + * * Loads the entities referenced in that field across all the entities being * viewed. */ @@ -110,15 +138,26 @@ public function prepareView(array $entities_items) { // For each item, pre-populate the loaded entity in $item->entity, and set // the 'loaded' flag. + $parent_entity_langcode = $items->getEntity()->language()->getId(); foreach ($entities_items as $items) { foreach ($items as $item) { if (isset($target_entities[$item->target_id])) { - $item->entity = $target_entities[$item->target_id]; + $entity = $target_entities[$item->target_id]; + // Set the entity in the correct language for display. + if ($entity instanceof TranslatableInterface && $entity->hasTranslation($parent_entity_langcode)) { + $entity = $entity->getTranslation($parent_entity_langcode); + } + $item->entity = $entity; $item->_loaded = TRUE; } elseif ($item->hasNewEntity()) { $item->_loaded = TRUE; } + + // For each item, pre-populate the entity access result. + if (!empty($item->_loaded)) { + $item->_access = $this->checkAccess($item); + } } } } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php index cb8a65e..69b5b9f 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php @@ -30,7 +30,7 @@ class EntityReferenceIdFormatter extends EntityReferenceFormatterBase { public function viewElements(FieldItemListInterface $items) { $elements = array(); - foreach ($this->getEntitiesToView($elements, $items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items) as $delta => $entity) { if ($entity->id()) { $elements[$delta] = array( '#markup' => SafeMarkup::checkPlain($entity->id()), diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php index c29194c..c362a80 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php @@ -64,8 +64,7 @@ public function viewElements(FieldItemListInterface $items) { $elements = array(); $output_as_link = $this->getSetting('link'); - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - foreach ($this->getEntitiesToView($elements, $items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items) as $delta => $entity) { $label = $entity->label(); // If the link is to be displayed and the entity has a uri, display a // link. diff --git a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php index c8623ce..7bd22f7 100644 --- a/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php +++ b/core/modules/field/src/Tests/EntityReference/EntityReferenceFormatterTest.php @@ -220,9 +220,7 @@ public function testLabelFormatter() { $build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter); $expected_field_cacheability = [ - 'contexts' => [ - 'user.permissions', - ], + 'contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT, ]; @@ -233,6 +231,9 @@ public function testLabelFormatter() { '#url' => $this->referencedEntity->urlInfo(), '#options' => $this->referencedEntity->urlInfo()->getOptions(), '#cache' => array( + 'contexts' => [ + 'user.permissions', + ], 'tags' => $this->referencedEntity->getCacheTags(), ), '#attached' => [], @@ -246,8 +247,14 @@ public function testLabelFormatter() { $expected_item_2 = array( '#markup' => $this->unsavedReferencedEntity->label(), '#cache' => array( + 'contexts' => [ + 'user.permissions', + ], 'tags' => $this->unsavedReferencedEntity->getCacheTags(), + 'max-age' => Cache::PERMANENT, ), + '#attached' => [], + '#post_render_cache' => [], ); $this->assertEqual($build[1], $expected_item_2, sprintf('The render array returned by the %s formatter is correct for an item with a unsaved entity.', $formatter)); diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php index 806f80a..cb917eb 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php @@ -28,7 +28,7 @@ class GenericFileFormatter extends FileFormatterBase { public function viewElements(FieldItemListInterface $items) { $elements = array(); - foreach ($this->getEntitiesToView($elements, $items) as $delta => $file) { + foreach ($this->getEntitiesToView($items) as $delta => $file) { $item = $file->_referringItem; $elements[$delta] = array( '#theme' => 'file_link', diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php index a8d6ec0..0a7b98f 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php @@ -30,7 +30,7 @@ public function viewElements(FieldItemListInterface $items) { $entity = $items->getEntity(); // Add the first file as an enclosure to the RSS item. RSS allows only one // enclosure per item. See: http://en.wikipedia.org/wiki/RSS_enclosure - foreach ($this->getEntitiesToView($elements, $items) as $delta => $file) { + foreach ($this->getEntitiesToView($items) as $delta => $file) { $entity->rss_elements[] = array( 'key' => 'enclosure', 'attributes' => array( diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php index fa4ed24..460450d 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php @@ -28,7 +28,7 @@ class TableFormatter extends FileFormatterBase { public function viewElements(FieldItemListInterface $items) { $elements = array(); - if ($files = $this->getEntitiesToView($elements, $items)) { + if ($files = $this->getEntitiesToView($items)) { $header = array(t('Attachment'), t('Size')); $rows = array(); foreach ($files as $delta => $file) { diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php index 39df54b..26b78f7 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php @@ -28,7 +28,7 @@ class UrlPlainFormatter extends FileFormatterBase { public function viewElements(FieldItemListInterface $items) { $elements = array(); - foreach ($this->getEntitiesToView($elements, $items) as $delta => $file) { + foreach ($this->getEntitiesToView($items) as $delta => $file) { $elements[$delta] = array('#markup' => file_create_url($file->getFileUri())); } diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 54b378f..38a163c 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -167,7 +167,7 @@ public function settingsSummary() { */ public function viewElements(FieldItemListInterface $items) { $elements = array(); - $files = $this->getEntitiesToView($elements, $items); + $files = $this->getEntitiesToView($items); // Early opt-out if the field is empty. if (empty($files)) { diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php index e637307..dd8348e 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php @@ -8,7 +8,6 @@ namespace Drupal\image\Plugin\Field\FieldFormatter; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; -use Drupal\Core\Render\BubbleableMetadata; use Drupal\field\FieldConfigInterface; use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase; @@ -20,7 +19,7 @@ /** * {@inheritdoc} */ - protected function getEntitiesToView(array &$build, EntityReferenceFieldItemListInterface $items) { + protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) { // Add the default image if needed. if ($items->isEmpty()) { $default_image = $this->getFieldSetting('default_image'); @@ -46,15 +45,10 @@ protected function getEntitiesToView(array &$build, EntityReferenceFieldItemList '_is_default' => TRUE, )); $file->_referringItem = $items[0]; - - // Add the fallback image's cache tags to the render array, so that if - // the fallback image is modified, all render cached fields displaying - // the fallback image are updated. - \Drupal::service('renderer')->addDependency($build, $file); } } - return parent::getEntitiesToView($build, $items); + return parent::getEntitiesToView($items); } } diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index edaee7d..bc8cb93 100644 --- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -175,7 +175,7 @@ public function settingsSummary() { */ public function viewElements(FieldItemListInterface $items) { $elements = array(); - $files = $this->getEntitiesToView($elements, $items); + $files = $this->getEntitiesToView($items); // Early opt-out if the field is empty. if (empty($files)) { diff --git a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php index 8a5816a..614f165 100644 --- a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php +++ b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php @@ -32,7 +32,7 @@ public function viewElements(FieldItemListInterface $items) { $parent_entity = $items->getEntity(); $elements = array(); - foreach ($this->getEntitiesToView($elements, $items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items) as $delta => $entity) { $parent_entity->rss_elements[] = array( 'key' => 'category', 'value' => $entity->label(),