74b15b65f4754ebff62267a668027cc4ba52c9e6
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Entity / EntityViewDisplay.php
1 <?php
2
3 namespace Drupal\Core\Entity\Entity;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
7 use Drupal\Core\Entity\EntityDisplayPluginCollection;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Entity\FieldableEntityInterface;
10 use Drupal\Core\Entity\EntityDisplayBase;
11 use Drupal\Core\TypedData\TranslatableInterface;
12
13 /**
14  * Configuration entity that contains display options for all components of a
15  * rendered entity in a given view mode.
16  *
17  * @ConfigEntityType(
18  *   id = "entity_view_display",
19  *   label = @Translation("Entity view display"),
20  *   entity_keys = {
21  *     "id" = "id",
22  *     "status" = "status"
23  *   },
24  *   config_export = {
25  *     "id",
26  *     "targetEntityType",
27  *     "bundle",
28  *     "mode",
29  *     "content",
30  *     "hidden",
31  *   }
32  * )
33  */
34 class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
35
36   /**
37    * {@inheritdoc}
38    */
39   protected $displayContext = 'view';
40
41   /**
42    * Returns the display objects used to render a set of entities.
43    *
44    * Depending on the configuration of the view mode for each bundle, this can
45    * be either the display object associated with the view mode, or the
46    * 'default' display.
47    *
48    * This method should only be used internally when rendering an entity. When
49    * assigning suggested display options for a component in a given view mode,
50    * entity_get_display() should be used instead, in order to avoid
51    * inadvertently modifying the output of other view modes that might happen to
52    * use the 'default' display too. Those options will then be effectively
53    * applied only if the view mode is configured to use them.
54    *
55    * hook_entity_view_display_alter() is invoked on each display, allowing 3rd
56    * party code to alter the display options held in the display before they are
57    * used to generate render arrays.
58    *
59    * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
60    *   The entities being rendered. They should all be of the same entity type.
61    * @param string $view_mode
62    *   The view mode being rendered.
63    *
64    * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
65    *   The display objects to use to render the entities, keyed by entity
66    *   bundle.
67    *
68    * @see entity_get_display()
69    * @see hook_entity_view_display_alter()
70    */
71   public static function collectRenderDisplays($entities, $view_mode) {
72     if (empty($entities)) {
73       return [];
74     }
75
76     // Collect entity type and bundles.
77     $entity_type = current($entities)->getEntityTypeId();
78     $bundles = [];
79     foreach ($entities as $entity) {
80       $bundles[$entity->bundle()] = TRUE;
81     }
82     $bundles = array_keys($bundles);
83
84     // For each bundle, check the existence and status of:
85     // - the display for the view mode,
86     // - the 'default' display.
87     $candidate_ids = [];
88     foreach ($bundles as $bundle) {
89       if ($view_mode != 'default') {
90         $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
91       }
92       $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
93     }
94     $results = \Drupal::entityQuery('entity_view_display')
95       ->condition('id', NestedArray::mergeDeepArray($candidate_ids))
96       ->condition('status', TRUE)
97       ->execute();
98
99     // For each bundle, select the first valid candidate display, if any.
100     $load_ids = [];
101     foreach ($bundles as $bundle) {
102       foreach ($candidate_ids[$bundle] as $candidate_id) {
103         if (isset($results[$candidate_id])) {
104           $load_ids[$bundle] = $candidate_id;
105           break;
106         }
107       }
108     }
109
110     // Load the selected displays.
111     $storage = \Drupal::entityManager()->getStorage('entity_view_display');
112     $displays = $storage->loadMultiple($load_ids);
113
114     $displays_by_bundle = [];
115     foreach ($bundles as $bundle) {
116       // Use the selected display if any, or create a fresh runtime object.
117       if (isset($load_ids[$bundle])) {
118         $display = $displays[$load_ids[$bundle]];
119       }
120       else {
121         $display = $storage->create([
122           'targetEntityType' => $entity_type,
123           'bundle' => $bundle,
124           'mode' => $view_mode,
125           'status' => TRUE,
126         ]);
127       }
128
129       // Let the display know which view mode was originally requested.
130       $display->originalMode = $view_mode;
131
132       // Let modules alter the display.
133       $display_context = [
134         'entity_type' => $entity_type,
135         'bundle' => $bundle,
136         'view_mode' => $view_mode,
137       ];
138       \Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
139
140       $displays_by_bundle[$bundle] = $display;
141     }
142
143     return $displays_by_bundle;
144   }
145
146   /**
147    * Returns the display object used to render an entity.
148    *
149    * See the collectRenderDisplays() method for details.
150    *
151    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
152    *   The entity being rendered.
153    * @param string $view_mode
154    *   The view mode.
155    *
156    * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
157    *   The display object that should be used to render the entity.
158    *
159    * @see \Drupal\Core\Entity\Entity\EntityViewDisplay::collectRenderDisplays()
160    */
161   public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) {
162     $displays = static::collectRenderDisplays([$entity], $view_mode);
163     return $displays[$entity->bundle()];
164   }
165
166   /**
167    * {@inheritdoc}
168    */
169   public function __construct(array $values, $entity_type) {
170     $this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
171
172     parent::__construct($values, $entity_type);
173   }
174
175   /**
176    * {@inheritdoc}
177    */
178   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
179     // Reset the render cache for the target entity type.
180     parent::postSave($storage, $update);
181     if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) {
182       \Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache();
183     }
184   }
185
186   /**
187    * {@inheritdoc}
188    */
189   public function getRenderer($field_name) {
190     if (isset($this->plugins[$field_name])) {
191       return $this->plugins[$field_name];
192     }
193
194     // Instantiate the formatter object from the stored display properties.
195     if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
196       $formatter = $this->pluginManager->getInstance([
197         'field_definition' => $definition,
198         'view_mode' => $this->originalMode,
199         // No need to prepare, defaults have been merged in setComponent().
200         'prepare' => FALSE,
201         'configuration' => $configuration
202       ]);
203     }
204     else {
205       $formatter = NULL;
206     }
207
208     // Persist the formatter object.
209     $this->plugins[$field_name] = $formatter;
210     return $formatter;
211   }
212
213   /**
214    * {@inheritdoc}
215    */
216   public function build(FieldableEntityInterface $entity) {
217     $build = $this->buildMultiple([$entity]);
218     return $build[0];
219   }
220
221   /**
222    * {@inheritdoc}
223    */
224   public function buildMultiple(array $entities) {
225     $build_list = [];
226     foreach ($entities as $key => $entity) {
227       $build_list[$key] = [];
228     }
229
230     // Run field formatters.
231     foreach ($this->getComponents() as $name => $options) {
232       if ($formatter = $this->getRenderer($name)) {
233         // Group items across all entities and pass them to the formatter's
234         // prepareView() method.
235         $grouped_items = [];
236         foreach ($entities as $id => $entity) {
237           $items = $entity->get($name);
238           $items->filterEmptyItems();
239           $grouped_items[$id] = $items;
240         }
241         $formatter->prepareView($grouped_items);
242
243         // Then let the formatter build the output for each entity.
244         foreach ($entities as $id => $entity) {
245           $items = $grouped_items[$id];
246           /** @var \Drupal\Core\Access\AccessResultInterface $field_access */
247           $field_access = $items->access('view', NULL, TRUE);
248           // The language of the field values to display is already determined
249           // in the incoming $entity. The formatter should build its output of
250           // those values using:
251           // - the entity language if the entity is translatable,
252           // - the current "content language" otherwise.
253           if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
254             $view_langcode = $entity->language()->getId();
255           }
256           else {
257             $view_langcode = NULL;
258           }
259           $build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $view_langcode) : [];
260           // Apply the field access cacheability metadata to the render array.
261           $this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);
262         }
263       }
264     }
265
266     foreach ($entities as $id => $entity) {
267       // Assign the configured weights.
268       foreach ($this->getComponents() as $name => $options) {
269         if (isset($build_list[$id][$name])) {
270           $build_list[$id][$name]['#weight'] = $options['weight'];
271         }
272       }
273
274       // Let other modules alter the renderable array.
275       $context = [
276         'entity' => $entity,
277         'view_mode' => $this->originalMode,
278         'display' => $this,
279       ];
280       \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$id], $context);
281     }
282
283     return $build_list;
284   }
285
286   /**
287    * {@inheritdoc}
288    */
289   public function getPluginCollections() {
290     $configurations = [];
291     foreach ($this->getComponents() as $field_name => $configuration) {
292       if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
293         $configurations[$configuration['type']] = $configuration + [
294           'field_definition' => $field_definition,
295           'view_mode' => $this->originalMode,
296         ];
297       }
298     }
299
300     return [
301       'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)
302     ];
303   }
304
305 }