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\TypedData\TranslatableInterface as TranslatableDataInterface;
14 * Configuration entity that contains display options for all components of a
15 * rendered entity in a given view mode.
18 * id = "entity_view_display",
19 * label = @Translation("Entity view display"),
25 * "access" = "\Drupal\Core\Entity\Entity\Access\EntityViewDisplayAccessControlHandler",
37 class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
42 protected $displayContext = 'view';
45 * Returns the display objects used to render a set of entities.
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
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.
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.
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.
67 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
68 * The display objects to use to render the entities, keyed by entity
71 * @see entity_get_display()
72 * @see hook_entity_view_display_alter()
74 public static function collectRenderDisplays($entities, $view_mode) {
75 if (empty($entities)) {
79 // Collect entity type and bundles.
80 $entity_type = current($entities)->getEntityTypeId();
82 foreach ($entities as $entity) {
83 $bundles[$entity->bundle()] = TRUE;
85 $bundles = array_keys($bundles);
87 // For each bundle, check the existence and status of:
88 // - the display for the view mode,
89 // - the 'default' display.
91 foreach ($bundles as $bundle) {
92 if ($view_mode != 'default') {
93 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
95 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
97 $results = \Drupal::entityQuery('entity_view_display')
98 ->condition('id', NestedArray::mergeDeepArray($candidate_ids))
99 ->condition('status', TRUE)
102 // For each bundle, select the first valid candidate display, if any.
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;
113 // Load the selected displays.
114 $storage = \Drupal::entityManager()->getStorage('entity_view_display');
115 $displays = $storage->loadMultiple($load_ids);
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]];
124 $display = $storage->create([
125 'targetEntityType' => $entity_type,
127 'mode' => $view_mode,
132 // Let the display know which view mode was originally requested.
133 $display->originalMode = $view_mode;
135 // Let modules alter the display.
137 'entity_type' => $entity_type,
139 'view_mode' => $view_mode,
141 \Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
143 $displays_by_bundle[$bundle] = $display;
146 return $displays_by_bundle;
150 * Returns the display object used to render an entity.
152 * See the collectRenderDisplays() method for details.
154 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
155 * The entity being rendered.
156 * @param string $view_mode
159 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
160 * The display object that should be used to render the entity.
162 * @see \Drupal\Core\Entity\Entity\EntityViewDisplay::collectRenderDisplays()
164 public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) {
165 $displays = static::collectRenderDisplays([$entity], $view_mode);
166 return $displays[$entity->bundle()];
172 public function __construct(array $values, $entity_type) {
173 $this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
175 parent::__construct($values, $entity_type);
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();
192 public function getRenderer($field_name) {
193 if (isset($this->plugins[$field_name])) {
194 return $this->plugins[$field_name];
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().
204 'configuration' => $configuration,
211 // Persist the formatter object.
212 $this->plugins[$field_name] = $formatter;
219 public function build(FieldableEntityInterface $entity) {
220 $build = $this->buildMultiple([$entity]);
227 public function buildMultiple(array $entities) {
229 foreach ($entities as $key => $entity) {
230 $build_list[$key] = [];
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.
239 foreach ($entities as $id => $entity) {
240 $items = $entity->get($name);
241 $items->filterEmptyItems();
242 $grouped_items[$id] = $items;
244 $formatter->prepareView($grouped_items);
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();
260 $view_langcode = NULL;
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);
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'];
277 // Let other modules alter the renderable array.
280 'view_mode' => $this->originalMode,
283 \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$id], $context);
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,
304 'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),