aa8f82934a37365ae33a2ee414a7e7a8dc5e9556
[yaffs-website] / web / core / lib / Drupal / Core / Field / Plugin / Field / FieldFormatter / EntityReferenceFormatterBase.php
1 <?php
2
3 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
4
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
8 use Drupal\Core\Field\FieldItemListInterface;
9 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
10 use Drupal\Core\Field\FormatterBase;
11 use Drupal\Core\TypedData\TranslatableInterface;
12
13 /**
14  * Parent plugin for entity reference formatters.
15  */
16 abstract class EntityReferenceFormatterBase extends FormatterBase {
17
18   /**
19    * Returns the referenced entities for display.
20    *
21    * The method takes care of:
22    * - checking entity access,
23    * - placing the entities in the language expected for display.
24    * It is thus strongly recommended that formatters use it in their
25    * implementation of viewElements($items) rather than dealing with $items
26    * directly.
27    *
28    * For each entity, the EntityReferenceItem by which the entity is referenced
29    * is available in $entity->_referringItem. This is useful for field types
30    * that store additional values next to the reference itself.
31    *
32    * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
33    *   The item list.
34    * @param string $langcode
35    *   The language code of the referenced entities to display.
36    *
37    * @return \Drupal\Core\Entity\EntityInterface[]
38    *   The array of referenced entities to display, keyed by delta.
39    *
40    * @see ::prepareView()
41    */
42   protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
43     $entities = [];
44
45     foreach ($items as $delta => $item) {
46       // Ignore items where no entity could be loaded in prepareView().
47       if (!empty($item->_loaded)) {
48         $entity = $item->entity;
49
50         // Set the entity in the correct language for display.
51         if ($entity instanceof TranslatableInterface) {
52           $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
53         }
54
55         $access = $this->checkAccess($entity);
56         // Add the access result's cacheability, ::view() needs it.
57         $item->_accessCacheability = CacheableMetadata::createFromObject($access);
58         if ($access->isAllowed()) {
59           // Add the referring item, in case the formatter needs it.
60           $entity->_referringItem = $items[$delta];
61           $entities[$delta] = $entity;
62         }
63       }
64     }
65
66     return $entities;
67   }
68
69   /**
70    * {@inheritdoc}
71    *
72    * @see ::prepareView()
73    * @see ::getEntitiestoView()
74    */
75   public function view(FieldItemListInterface $items, $langcode = NULL) {
76     $elements = parent::view($items, $langcode);
77
78     $field_level_access_cacheability = new CacheableMetadata();
79
80     // Try to map the cacheability of the access result that was set at
81     // _accessCacheability in getEntitiesToView() to the corresponding render
82     // subtree. If no such subtree is found, then merge it with the field-level
83     // access cacheability.
84     foreach ($items as $delta => $item) {
85       // Ignore items for which access cacheability could not be determined in
86       // prepareView().
87       if (!empty($item->_accessCacheability)) {
88         if (isset($elements[$delta])) {
89           CacheableMetadata::createFromRenderArray($elements[$delta])
90             ->merge($item->_accessCacheability)
91             ->applyTo($elements[$delta]);
92         }
93         else {
94           $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability);
95         }
96       }
97     }
98
99     // Apply the cacheability metadata for the inaccessible entities and the
100     // entities for which the corresponding render subtree could not be found.
101     // This causes the field to be rendered (and cached) according to the cache
102     // contexts by which the access results vary, to ensure only users with
103     // access to this field can view it. It also tags this field with the cache
104     // tags on which the access results depend, to ensure users that cannot view
105     // this field at the moment will gain access once any of those cache tags
106     // are invalidated.
107     $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements))
108       ->applyTo($elements);
109
110     return $elements;
111   }
112
113   /**
114    * {@inheritdoc}
115    *
116    * Loads the entities referenced in that field across all the entities being
117    * viewed.
118    */
119   public function prepareView(array $entities_items) {
120     // Collect entity IDs to load. For performance, we want to use a single
121     // "multiple entity load" to load all the entities for the multiple
122     // "entity reference item lists" being displayed. We thus cannot use
123     // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
124     $ids = [];
125     foreach ($entities_items as $items) {
126       foreach ($items as $item) {
127         // To avoid trying to reload non-existent entities in
128         // getEntitiesToView(), explicitly mark the items where $item->entity
129         // contains a valid entity ready for display. All items are initialized
130         // at FALSE.
131         $item->_loaded = FALSE;
132         if ($this->needsEntityLoad($item)) {
133           $ids[] = $item->target_id;
134         }
135       }
136     }
137     if ($ids) {
138       $target_type = $this->getFieldSetting('target_type');
139       $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids);
140     }
141
142     // For each item, pre-populate the loaded entity in $item->entity, and set
143     // the 'loaded' flag.
144     foreach ($entities_items as $items) {
145       foreach ($items as $item) {
146         if (isset($target_entities[$item->target_id])) {
147           $item->entity = $target_entities[$item->target_id];
148           $item->_loaded = TRUE;
149         }
150         elseif ($item->hasNewEntity()) {
151           $item->_loaded = TRUE;
152         }
153       }
154     }
155   }
156
157   /**
158    * Returns whether the entity referenced by an item needs to be loaded.
159    *
160    * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
161    *    The item to check.
162    *
163    * @return bool
164    *   TRUE if the entity needs to be loaded.
165    */
166   protected function needsEntityLoad(EntityReferenceItem $item) {
167     return !$item->hasNewEntity();
168   }
169
170   /**
171    * Checks access to the given entity.
172    *
173    * By default, entity 'view' access is checked. However, a subclass can choose
174    * to exclude certain items from entity access checking by immediately
175    * granting access.
176    *
177    * @param \Drupal\Core\Entity\EntityInterface $entity
178    *    The entity to check.
179    *
180    * @return \Drupal\Core\Access\AccessResult
181    *   A cacheable access result.
182    */
183   protected function checkAccess(EntityInterface $entity) {
184     return $entity->access('view', NULL, TRUE);
185   }
186
187 }