EntityReferenceFormatterBase.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
  3. use Drupal\Core\Cache\CacheableMetadata;
  4. use Drupal\Core\Entity\EntityInterface;
  5. use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
  6. use Drupal\Core\Field\FieldItemListInterface;
  7. use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
  8. use Drupal\Core\Field\FormatterBase;
  9. use Drupal\Core\TypedData\TranslatableInterface;
  10. /**
  11. * Parent plugin for entity reference formatters.
  12. */
  13. abstract class EntityReferenceFormatterBase extends FormatterBase {
  14. /**
  15. * Returns the referenced entities for display.
  16. *
  17. * The method takes care of:
  18. * - checking entity access,
  19. * - placing the entities in the language expected for display.
  20. * It is thus strongly recommended that formatters use it in their
  21. * implementation of viewElements($items) rather than dealing with $items
  22. * directly.
  23. *
  24. * For each entity, the EntityReferenceItem by which the entity is referenced
  25. * is available in $entity->_referringItem. This is useful for field types
  26. * that store additional values next to the reference itself.
  27. *
  28. * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
  29. * The item list.
  30. * @param string $langcode
  31. * The language code of the referenced entities to display.
  32. *
  33. * @return \Drupal\Core\Entity\EntityInterface[]
  34. * The array of referenced entities to display, keyed by delta.
  35. *
  36. * @see ::prepareView()
  37. */
  38. protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
  39. $entities = [];
  40. foreach ($items as $delta => $item) {
  41. // Ignore items where no entity could be loaded in prepareView().
  42. if (!empty($item->_loaded)) {
  43. $entity = $item->entity;
  44. // Set the entity in the correct language for display.
  45. if ($entity instanceof TranslatableInterface) {
  46. $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
  47. }
  48. $access = $this->checkAccess($entity);
  49. // Add the access result's cacheability, ::view() needs it.
  50. $item->_accessCacheability = CacheableMetadata::createFromObject($access);
  51. if ($access->isAllowed()) {
  52. // Add the referring item, in case the formatter needs it.
  53. $entity->_referringItem = $items[$delta];
  54. $entities[$delta] = $entity;
  55. }
  56. }
  57. }
  58. return $entities;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. *
  63. * @see ::prepareView()
  64. * @see ::getEntitiestoView()
  65. */
  66. public function view(FieldItemListInterface $items, $langcode = NULL) {
  67. $elements = parent::view($items, $langcode);
  68. $field_level_access_cacheability = new CacheableMetadata();
  69. // Try to map the cacheability of the access result that was set at
  70. // _accessCacheability in getEntitiesToView() to the corresponding render
  71. // subtree. If no such subtree is found, then merge it with the field-level
  72. // access cacheability.
  73. foreach ($items as $delta => $item) {
  74. // Ignore items for which access cacheability could not be determined in
  75. // prepareView().
  76. if (!empty($item->_accessCacheability)) {
  77. if (isset($elements[$delta])) {
  78. CacheableMetadata::createFromRenderArray($elements[$delta])
  79. ->merge($item->_accessCacheability)
  80. ->applyTo($elements[$delta]);
  81. }
  82. else {
  83. $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability);
  84. }
  85. }
  86. }
  87. // Apply the cacheability metadata for the inaccessible entities and the
  88. // entities for which the corresponding render subtree could not be found.
  89. // This causes the field to be rendered (and cached) according to the cache
  90. // contexts by which the access results vary, to ensure only users with
  91. // access to this field can view it. It also tags this field with the cache
  92. // tags on which the access results depend, to ensure users that cannot view
  93. // this field at the moment will gain access once any of those cache tags
  94. // are invalidated.
  95. $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements))
  96. ->applyTo($elements);
  97. return $elements;
  98. }
  99. /**
  100. * {@inheritdoc}
  101. *
  102. * Loads the entities referenced in that field across all the entities being
  103. * viewed.
  104. */
  105. public function prepareView(array $entities_items) {
  106. // Collect entity IDs to load. For performance, we want to use a single
  107. // "multiple entity load" to load all the entities for the multiple
  108. // "entity reference item lists" being displayed. We thus cannot use
  109. // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
  110. $ids = [];
  111. foreach ($entities_items as $items) {
  112. foreach ($items as $item) {
  113. // To avoid trying to reload non-existent entities in
  114. // getEntitiesToView(), explicitly mark the items where $item->entity
  115. // contains a valid entity ready for display. All items are initialized
  116. // at FALSE.
  117. $item->_loaded = FALSE;
  118. if ($this->needsEntityLoad($item)) {
  119. $ids[] = $item->target_id;
  120. }
  121. }
  122. }
  123. if ($ids) {
  124. $target_type = $this->getFieldSetting('target_type');
  125. $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids);
  126. }
  127. // For each item, pre-populate the loaded entity in $item->entity, and set
  128. // the 'loaded' flag.
  129. foreach ($entities_items as $items) {
  130. foreach ($items as $item) {
  131. if (isset($target_entities[$item->target_id])) {
  132. $item->entity = $target_entities[$item->target_id];
  133. $item->_loaded = TRUE;
  134. }
  135. elseif ($item->hasNewEntity()) {
  136. $item->_loaded = TRUE;
  137. }
  138. }
  139. }
  140. }
  141. /**
  142. * Returns whether the entity referenced by an item needs to be loaded.
  143. *
  144. * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
  145. * The item to check.
  146. *
  147. * @return bool
  148. * TRUE if the entity needs to be loaded.
  149. */
  150. protected function needsEntityLoad(EntityReferenceItem $item) {
  151. return !$item->hasNewEntity();
  152. }
  153. /**
  154. * Checks access to the given entity.
  155. *
  156. * By default, entity 'view' access is checked. However, a subclass can choose
  157. * to exclude certain items from entity access checking by immediately
  158. * granting access.
  159. *
  160. * @param \Drupal\Core\Entity\EntityInterface $entity
  161. * The entity to check.
  162. *
  163. * @return \Drupal\Core\Access\AccessResult
  164. * A cacheable access result.
  165. */
  166. protected function checkAccess(EntityInterface $entity) {
  167. return $entity->access('view', NULL, TRUE);
  168. }
  169. }