Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Field / Plugin / Field / FieldFormatter / EntityReferenceFormatterBase.php
diff --git a/web/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/web/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php
new file mode 100644 (file)
index 0000000..aa8f829
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Cache\CacheableMetadata;
+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\TypedData\TranslatableInterface;
+
+/**
+ * Parent plugin for entity reference formatters.
+ */
+abstract class EntityReferenceFormatterBase extends FormatterBase {
+
+  /**
+   * Returns the referenced entities for display.
+   *
+   * The method takes care of:
+   * - checking entity access,
+   * - placing the entities in the language expected for display.
+   * It is thus strongly recommended that formatters use it in their
+   * implementation of viewElements($items) rather than dealing with $items
+   * directly.
+   *
+   * For each entity, the EntityReferenceItem by which the entity is referenced
+   * is available in $entity->_referringItem. This is useful for field types
+   * that store additional values next to the reference itself.
+   *
+   * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
+   *   The item list.
+   * @param string $langcode
+   *   The language code of the referenced entities to display.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *   The array of referenced entities to display, keyed by delta.
+   *
+   * @see ::prepareView()
+   */
+  protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
+    $entities = [];
+
+    foreach ($items as $delta => $item) {
+      // Ignore items where no entity could be loaded in prepareView().
+      if (!empty($item->_loaded)) {
+        $entity = $item->entity;
+
+        // Set the entity in the correct language for display.
+        if ($entity instanceof TranslatableInterface) {
+          $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
+        }
+
+        $access = $this->checkAccess($entity);
+        // Add the access result's cacheability, ::view() needs it.
+        $item->_accessCacheability = CacheableMetadata::createFromObject($access);
+        if ($access->isAllowed()) {
+          // Add the referring item, in case the formatter needs it.
+          $entity->_referringItem = $items[$delta];
+          $entities[$delta] = $entity;
+        }
+      }
+    }
+
+    return $entities;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @see ::prepareView()
+   * @see ::getEntitiestoView()
+   */
+  public function view(FieldItemListInterface $items, $langcode = NULL) {
+    $elements = parent::view($items, $langcode);
+
+    $field_level_access_cacheability = new CacheableMetadata();
+
+    // Try to map the cacheability of the access result that was set at
+    // _accessCacheability in getEntitiesToView() 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 for which access cacheability could not be determined in
+      // prepareView().
+      if (!empty($item->_accessCacheability)) {
+        if (isset($elements[$delta])) {
+          CacheableMetadata::createFromRenderArray($elements[$delta])
+            ->merge($item->_accessCacheability)
+            ->applyTo($elements[$delta]);
+        }
+        else {
+          $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability);
+        }
+      }
+    }
+
+    // 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 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.
+    $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements))
+      ->applyTo($elements);
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Loads the entities referenced in that field across all the entities being
+   * viewed.
+   */
+  public function prepareView(array $entities_items) {
+    // Collect entity IDs to load. For performance, we want to use a single
+    // "multiple entity load" to load all the entities for the multiple
+    // "entity reference item lists" being displayed. We thus cannot use
+    // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
+    $ids = [];
+    foreach ($entities_items as $items) {
+      foreach ($items as $item) {
+        // To avoid trying to reload non-existent entities in
+        // getEntitiesToView(), explicitly mark the items where $item->entity
+        // contains a valid entity ready for display. All items are initialized
+        // at FALSE.
+        $item->_loaded = FALSE;
+        if ($this->needsEntityLoad($item)) {
+          $ids[] = $item->target_id;
+        }
+      }
+    }
+    if ($ids) {
+      $target_type = $this->getFieldSetting('target_type');
+      $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids);
+    }
+
+    // For each item, pre-populate the loaded entity in $item->entity, and set
+    // the 'loaded' flag.
+    foreach ($entities_items as $items) {
+      foreach ($items as $item) {
+        if (isset($target_entities[$item->target_id])) {
+          $item->entity = $target_entities[$item->target_id];
+          $item->_loaded = TRUE;
+        }
+        elseif ($item->hasNewEntity()) {
+          $item->_loaded = TRUE;
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns whether the entity referenced by an item needs to be loaded.
+   *
+   * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
+   *    The item to check.
+   *
+   * @return bool
+   *   TRUE if the entity needs to be loaded.
+   */
+  protected function needsEntityLoad(EntityReferenceItem $item) {
+    return !$item->hasNewEntity();
+  }
+
+  /**
+   * Checks access to the given entity.
+   *
+   * By default, entity 'view' access is checked. However, a subclass can choose
+   * to exclude certain items from entity access checking by immediately
+   * granting access.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *    The entity to check.
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   *   A cacheable access result.
+   */
+  protected function checkAccess(EntityInterface $entity) {
+    return $entity->access('view', NULL, TRUE);
+  }
+
+}