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