Newer
Older
Angie Byron
committed
<?php
/**
* @file
Alex Pott
committed
* Contains \Drupal\Core\Entity\EntityViewBuilder.
Angie Byron
committed
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManagerInterface;
catch
committed
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\entity\Entity\EntityViewDisplay;
use Symfony\Component\DependencyInjection\ContainerInterface;
Angie Byron
committed
/**
* Base class for entity view controllers.
*/
class EntityViewBuilder extends EntityControllerBase implements EntityControllerInterface, EntityViewBuilderInterface {
Angie Byron
committed
/**
* The type of entities for which this controller is instantiated.
*
* @var string
*/
protected $entityTypeId;
Angie Byron
committed
/**
* Information about the entity type.
Alex Pott
committed
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The cache bin used to store the render cache.
*
* @var string
*/
protected $cacheBin = 'render';
/**
* The language manager.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
*/
protected $languageManager;
/**
* Constructs a new EntityViewBuilder.
*
Alex Pott
committed
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
Alex Pott
committed
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
Alex Pott
committed
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->entityManager = $entity_manager;
$this->languageManager = $language_manager;
Angie Byron
committed
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
Alex Pott
committed
return new static(
Alex Pott
committed
$entity_type,
$container->get('entity.manager'),
$container->get('language_manager')
Alex Pott
committed
);
}
Angie Byron
committed
/**
Alex Pott
committed
* {@inheritdoc}
Angie Byron
committed
*/
Angie Byron
committed
public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
$entities_by_bundle = array();
foreach ($entities as $id => $entity) {
// Remove previously built content, if exists.
$entity->content = array(
'#view_mode' => $view_mode,
);
// Initialize the field item attributes for the fields being displayed.
// The entity can include fields that are not displayed, and the display
// can include components that are not fields, so we want to act on the
// intersection. However, the entity can have many more fields than are
// displayed, so we avoid the cost of calling $entity->getProperties()
// by iterating the intersection as follows.
foreach ($displays[$entity->bundle()]->getComponents() as $name => $options) {
if ($entity->hasField($name)) {
foreach ($entity->get($name) as $item) {
$item->_attributes = array();
}
}
}
// Group the entities by bundle.
$entities_by_bundle[$entity->bundle()][$id] = $entity;
}
// Invoke hook_entity_prepare_view().
\Drupal::moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode));
// Let the displays build their render arrays.
foreach ($entities_by_bundle as $bundle => $bundle_entities) {
$build = $displays[$bundle]->buildMultiple($bundle_entities);
foreach ($bundle_entities as $id => $entity) {
$entity->content += $build[$id];
}
Angie Byron
committed
}
}
/**
* Provides entity-specific defaults to the build process.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
Angie Byron
committed
* The entity for which the defaults should be provided.
* @param string $view_mode
* The view mode that should be used.
* @param string $langcode
* (optional) For which language the entity should be prepared, defaults to
* the current content language.
*
* @return array
*/
protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
$return = array(
'#theme' => $this->entityTypeId,
"#{$this->entityTypeId}" => $entity,
Angie Byron
committed
'#view_mode' => $view_mode,
'#langcode' => $langcode,
'#cache' => array(
'tags' => array(
$this->entityTypeId . '_view' => TRUE,
$this->entityTypeId => array($entity->id()),
),
)
Angie Byron
committed
);
// Cache the rendered output if permitted by the view mode and global entity
// type configuration.
catch
committed
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
$return['#cache'] += array(
'keys' => array(
'entity_view',
$this->entityTypeId,
$entity->id(),
$view_mode,
'cache_context.theme',
'cache_context.user.roles',
),
'bin' => $this->cacheBin,
);
catch
committed
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
$return['#cache']['keys'][] = $langcode;
}
}
Angie Byron
committed
return $return;
}
/**
* Specific per-entity building.
*
* @param array $build
* The render array that is being created.
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $entity
Angie Byron
committed
* The entity to be prepared.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
Alex Pott
committed
* The entity view display holding the display options configured for the
* entity components.
Angie Byron
committed
* @param string $view_mode
* The view mode that should be used to prepare the entity.
* @param string $langcode
* (optional) For which language the entity should be prepared, defaults to
* the current content language.
*/
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode = NULL) { }
Angie Byron
committed
/**
Alex Pott
committed
* {@inheritdoc}
Angie Byron
committed
*/
public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
$buildList = $this->viewMultiple(array($entity), $view_mode, $langcode);
return $buildList[0];
}
/**
Alex Pott
committed
* {@inheritdoc}
Angie Byron
committed
*/
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $this->languageManager->getCurrentLanguage(Language::TYPE_CONTENT)->id;
Angie Byron
committed
}
Angie Byron
committed
// Build the view modes and display objects.
$view_modes = array();
$context = array('langcode' => $langcode);
foreach ($entities as $key => $entity) {
Angie Byron
committed
$bundle = $entity->bundle();
// Ensure that from now on we are dealing with the proper translation
// object.
$entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
$entities[$key] = $entity;
Angie Byron
committed
// Allow modules to change the view mode.
$entity_view_mode = $view_mode;
$this->moduleHandler->alter('entity_view_mode', $entity_view_mode, $entity, $context);
Angie Byron
committed
// Store entities for rendering by view_mode.
$view_modes[$entity_view_mode][$entity->id()] = $entity;
}
foreach ($view_modes as $mode => $view_mode_entities) {
$displays[$mode] = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $mode);
Angie Byron
committed
$this->buildContent($view_mode_entities, $displays[$mode], $mode, $langcode);
}
Angie Byron
committed
$view_hook = "{$this->entityTypeId}_view";
Angie Byron
committed
$build = array('#sorted' => TRUE);
$weight = 0;
foreach ($entities as $key => $entity) {
$entity_view_mode = isset($entity->content['#view_mode']) ? $entity->content['#view_mode'] : $view_mode;
Angie Byron
committed
$display = $displays[$entity_view_mode][$entity->bundle()];
\Drupal::moduleHandler()->invokeAll($view_hook, array($entity, $display, $entity_view_mode, $langcode));
\Drupal::moduleHandler()->invokeAll('entity_view', array($entity, $display, $entity_view_mode, $langcode));
Angie Byron
committed
$build[$key] = $entity->content;
// We don't need duplicate rendering info in $entity->content.
unset($entity->content);
$build[$key] += $this->getBuildDefaults($entity, $entity_view_mode, $langcode);
Angie Byron
committed
$this->alterBuild($build[$key], $entity, $display, $entity_view_mode, $langcode);
// Assign the weights configured in the display.
// @todo: Once https://drupal.org/node/1875974 provides the missing API,
// only do it for 'extra fields', since other components have been taken
// care of in EntityViewDisplay::buildMultiple().
Angie Byron
committed
foreach ($display->getComponents() as $name => $options) {
if (isset($build[$key][$name])) {
$build[$key][$name]['#weight'] = $options['weight'];
}
}
Angie Byron
committed
$build[$key]['#weight'] = $weight++;
Angie Byron
committed
// Allow modules to modify the render array.
$this->moduleHandler->alter(array($view_hook, 'entity_view'), $build[$key], $entity, $display);
Angie Byron
committed
}
return $build;
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function resetCache(array $entities = NULL) {
if (isset($entities)) {
catch
committed
// Always invalidate the ENTITY_TYPE_list tag.
$tags = array($this->entityTypeId . '_list' => TRUE);
Dries Buytaert
committed
foreach ($entities as $entity) {
$id = $entity->id();
$tags[$this->entityTypeId][$id] = $id;
$tags[$this->entityTypeId . '_view_' . $entity->bundle()] = TRUE;
}
Cache::deleteTags($tags);
}
else {
Cache::deleteTags(array($this->entityTypeId . '_view' => TRUE));
}
}
/**
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
* {@inheritdoc}
*/
public function viewField(FieldItemListInterface $items, $display_options = array()) {
$output = array();
$entity = $items->getEntity();
$field_name = $items->getFieldDefinition()->getName();
// Get the display object.
if (is_string($display_options)) {
$view_mode = $display_options;
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
foreach ($entity as $name => $items) {
if ($name != $field_name) {
$display->removeComponent($name);
}
}
}
else {
$view_mode = '_custom';
$display = entity_create('entity_view_display', array(
'targetEntityType' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'mode' => $view_mode,
'status' => TRUE,
));
$display->setComponent($field_name, $display_options);
}
$build = $display->build($entity);
if (isset($build[$field_name])) {
$output = $build[$field_name];
}
return $output;
}
/**
* {@inheritdoc}
*/
public function viewFieldItem(FieldItemInterface $item, $display = array()) {
$entity = $item->getEntity();
$field_name = $item->getFieldDefinition()->getName();
// Clone the entity since we are going to modify field values.
$clone = clone $entity;
// Push the item as the single value for the field, and defer to viewField()
// to build the render array for the whole list.
$clone->{$field_name}->setValue(array($item->getValue()));
$elements = $this->viewField($clone->{$field_name}, $display);
// Extract the part of the render array we need.
$output = isset($elements[0]) ? $elements[0] : array();
if (isset($elements['#access'])) {
$output['#access'] = $elements['#access'];
}
return $output;
}
/*
* Returns TRUE if the view mode is cacheable.
*
* @param string $view_mode
* Name of the view mode that should be rendered.
*
* @return bool
* TRUE if the view mode can be cached, FALSE otherwise.
*/
protected function isViewModeCacheable($view_mode) {
if ($view_mode == 'default') {
// The 'default' is not an actual view mode.
return TRUE;
}
$view_modes_info = $this->entityManager->getViewModes($this->entityTypeId);
return !empty($view_modes_info[$view_mode]['cache']);
}