EntityViewDisplay.php 10 KB

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