3 namespace Drupal\Core\Entity\Entity;
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;
15 * Configuration entity that contains display options for all components of a
16 * rendered entity in a given view mode.
19 * id = "entity_view_display",
20 * label = @Translation("Entity view display"),
26 * "access" = "\Drupal\Core\Entity\Entity\Access\EntityViewDisplayAccessControlHandler",
38 class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
43 protected $displayContext = 'view';
46 * Returns the display objects used to render a set of entities.
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
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.
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.
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.
68 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
69 * The display objects to use to render the entities, keyed by entity
72 * @see entity_get_display()
73 * @see hook_entity_view_display_alter()
75 public static function collectRenderDisplays($entities, $view_mode) {
76 if (empty($entities)) {
80 // Collect entity type and bundles.
81 $entity_type = current($entities)->getEntityTypeId();
83 foreach ($entities as $entity) {
84 $bundles[$entity->bundle()] = TRUE;
86 $bundles = array_keys($bundles);
88 // For each bundle, check the existence and status of:
89 // - the display for the view mode,
90 // - the 'default' display.
92 foreach ($bundles as $bundle) {
93 if ($view_mode != 'default') {
94 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
96 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
98 $results = \Drupal::entityQuery('entity_view_display')
99 ->condition('id', NestedArray::mergeDeepArray($candidate_ids))
100 ->condition('status', TRUE)
103 // For each bundle, select the first valid candidate display, if any.
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;
114 // Load the selected displays.
115 $storage = \Drupal::entityManager()->getStorage('entity_view_display');
116 $displays = $storage->loadMultiple($load_ids);
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]];
125 $display = $storage->create([
126 'targetEntityType' => $entity_type,
128 'mode' => $view_mode,
133 // Let the display know which view mode was originally requested.
134 $display->originalMode = $view_mode;
136 // Let modules alter the display.
138 'entity_type' => $entity_type,
140 'view_mode' => $view_mode,
142 \Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
144 $displays_by_bundle[$bundle] = $display;
147 return $displays_by_bundle;
151 * Returns the display object used to render an entity.
153 * See the collectRenderDisplays() method for details.
155 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
156 * The entity being rendered.
157 * @param string $view_mode
160 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
161 * The display object that should be used to render the entity.
163 * @see \Drupal\Core\Entity\Entity\EntityViewDisplay::collectRenderDisplays()
165 public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) {
166 $displays = static::collectRenderDisplays([$entity], $view_mode);
167 return $displays[$entity->bundle()];
173 public function __construct(array $values, $entity_type) {
174 $this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
176 parent::__construct($values, $entity_type);
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();
193 public function getRenderer($field_name) {
194 if (isset($this->plugins[$field_name])) {
195 return $this->plugins[$field_name];
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().
205 'configuration' => $configuration,
212 // Persist the formatter object.
213 $this->plugins[$field_name] = $formatter;
220 public function build(FieldableEntityInterface $entity) {
221 $build = $this->buildMultiple([$entity]);
228 public function buildMultiple(array $entities) {
230 foreach ($entities as $key => $entity) {
231 $build_list[$key] = [];
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.
240 foreach ($entities as $id => $entity) {
241 $items = $entity->get($name);
242 $items->filterEmptyItems();
243 $grouped_items[$id] = $items;
245 $formatter->prepareView($grouped_items);
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();
261 $view_langcode = NULL;
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);
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'];
278 // Let other modules alter the renderable array.
281 'view_mode' => $this->originalMode,
284 \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$id], $context);
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,
305 'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),