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