123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- <?php
- namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
- use Drupal\Core\Cache\CacheableMetadata;
- use Drupal\Core\Entity\EntityInterface;
- use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
- use Drupal\Core\Field\FieldItemListInterface;
- use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
- use Drupal\Core\Field\FormatterBase;
- use Drupal\Core\TypedData\TranslatableInterface;
- /**
- * Parent plugin for entity reference formatters.
- */
- abstract class EntityReferenceFormatterBase extends FormatterBase {
- /**
- * Returns the referenced entities for display.
- *
- * The method takes care of:
- * - checking entity access,
- * - placing the entities in the language expected for display.
- * It is thus strongly recommended that formatters use it in their
- * implementation of viewElements($items) rather than dealing with $items
- * directly.
- *
- * For each entity, the EntityReferenceItem by which the entity is referenced
- * is available in $entity->_referringItem. This is useful for field types
- * that store additional values next to the reference itself.
- *
- * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
- * The item list.
- * @param string $langcode
- * The language code of the referenced entities to display.
- *
- * @return \Drupal\Core\Entity\EntityInterface[]
- * The array of referenced entities to display, keyed by delta.
- *
- * @see ::prepareView()
- */
- protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
- $entities = [];
- foreach ($items as $delta => $item) {
- // Ignore items where no entity could be loaded in prepareView().
- if (!empty($item->_loaded)) {
- $entity = $item->entity;
- // Set the entity in the correct language for display.
- if ($entity instanceof TranslatableInterface) {
- $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
- }
- $access = $this->checkAccess($entity);
- // Add the access result's cacheability, ::view() needs it.
- $item->_accessCacheability = CacheableMetadata::createFromObject($access);
- if ($access->isAllowed()) {
- // Add the referring item, in case the formatter needs it.
- $entity->_referringItem = $items[$delta];
- $entities[$delta] = $entity;
- }
- }
- }
- return $entities;
- }
- /**
- * {@inheritdoc}
- *
- * @see ::prepareView()
- * @see ::getEntitiestoView()
- */
- public function view(FieldItemListInterface $items, $langcode = NULL) {
- $elements = parent::view($items, $langcode);
- $field_level_access_cacheability = new CacheableMetadata();
- // Try to map the cacheability of the access result that was set at
- // _accessCacheability in getEntitiesToView() to the corresponding render
- // subtree. If no such subtree is found, then merge it with the field-level
- // access cacheability.
- foreach ($items as $delta => $item) {
- // Ignore items for which access cacheability could not be determined in
- // prepareView().
- if (!empty($item->_accessCacheability)) {
- if (isset($elements[$delta])) {
- CacheableMetadata::createFromRenderArray($elements[$delta])
- ->merge($item->_accessCacheability)
- ->applyTo($elements[$delta]);
- }
- else {
- $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability);
- }
- }
- }
- // Apply the cacheability metadata for the inaccessible entities and the
- // entities for which the corresponding render subtree could not be found.
- // This causes the field to be rendered (and cached) according to the cache
- // contexts by which the access results vary, to ensure only users with
- // access to this field can view it. It also tags this field with the cache
- // tags on which the access results depend, to ensure users that cannot view
- // this field at the moment will gain access once any of those cache tags
- // are invalidated.
- $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements))
- ->applyTo($elements);
- return $elements;
- }
- /**
- * {@inheritdoc}
- *
- * Loads the entities referenced in that field across all the entities being
- * viewed.
- */
- public function prepareView(array $entities_items) {
- // Collect entity IDs to load. For performance, we want to use a single
- // "multiple entity load" to load all the entities for the multiple
- // "entity reference item lists" being displayed. We thus cannot use
- // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
- $ids = [];
- foreach ($entities_items as $items) {
- foreach ($items as $item) {
- // To avoid trying to reload non-existent entities in
- // getEntitiesToView(), explicitly mark the items where $item->entity
- // contains a valid entity ready for display. All items are initialized
- // at FALSE.
- $item->_loaded = FALSE;
- if ($this->needsEntityLoad($item)) {
- $ids[] = $item->target_id;
- }
- }
- }
- if ($ids) {
- $target_type = $this->getFieldSetting('target_type');
- $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids);
- }
- // For each item, pre-populate the loaded entity in $item->entity, and set
- // the 'loaded' flag.
- foreach ($entities_items as $items) {
- foreach ($items as $item) {
- if (isset($target_entities[$item->target_id])) {
- $item->entity = $target_entities[$item->target_id];
- $item->_loaded = TRUE;
- }
- elseif ($item->hasNewEntity()) {
- $item->_loaded = TRUE;
- }
- }
- }
- }
- /**
- * Returns whether the entity referenced by an item needs to be loaded.
- *
- * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
- * The item to check.
- *
- * @return bool
- * TRUE if the entity needs to be loaded.
- */
- protected function needsEntityLoad(EntityReferenceItem $item) {
- return !$item->hasNewEntity();
- }
- /**
- * Checks access to the given entity.
- *
- * By default, entity 'view' access is checked. However, a subclass can choose
- * to exclude certain items from entity access checking by immediately
- * granting access.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity to check.
- *
- * @return \Drupal\Core\Access\AccessResult
- * A cacheable access result.
- */
- protected function checkAccess(EntityInterface $entity) {
- return $entity->access('view', NULL, TRUE);
- }
- }
|