Chris@0: 'entity.manager']; Chris@0: Chris@0: /** Chris@0: * The relationship being handled. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $relationship; Chris@0: Chris@0: /** Chris@18: * The entity type manager. Chris@0: * Chris@18: * @var \Drupal\Core\Entity\EntityTypeManagerInterface Chris@0: */ Chris@18: protected $entityTypeManager; Chris@18: Chris@18: /** Chris@18: * The entity repository service. Chris@18: * Chris@18: * @var \Drupal\Core\Entity\EntityRepositoryInterface Chris@18: */ Chris@18: protected $entityRepository; Chris@0: Chris@0: /** Chris@0: * A list of indexes of rows whose fields have already been rendered. Chris@0: * Chris@0: * @var int[] Chris@0: */ Chris@0: protected $processedRows = []; Chris@0: Chris@0: /** Chris@0: * Constructs an EntityFieldRenderer object. Chris@0: * Chris@0: * @param \Drupal\views\ViewExecutable $view Chris@0: * The view whose fields are being rendered. Chris@0: * @param string $relationship Chris@0: * The relationship to be handled. Chris@0: * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager Chris@0: * The language manager. Chris@0: * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type Chris@0: * The entity type. Chris@18: * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager Chris@18: * The entity type manager. Chris@18: * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository Chris@18: * The entity repository. Chris@0: */ Chris@18: public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository = NULL) { Chris@0: parent::__construct($view, $language_manager, $entity_type); Chris@0: $this->relationship = $relationship; Chris@18: $this->entityTypeManager = $entity_type_manager; Chris@18: if (!$entity_repository) { Chris@18: @trigger_error('Calling EntityFieldRenderer::__construct() with the $entity_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED); Chris@18: $entity_repository = \Drupal::service('entity.repository'); Chris@18: } Chris@18: $this->entityRepository = $entity_repository; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheContexts() { Chris@0: return $this->getEntityTranslationRenderer()->getCacheContexts(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getEntityTypeId() { Chris@0: return $this->entityType->id(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getEntityManager() { Chris@18: // This relies on DeprecatedServicePropertyTrait to trigger a deprecation Chris@18: // message in case it is accessed. Chris@0: return $this->entityManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@18: protected function getEntityTypeManager() { Chris@18: return $this->entityTypeManager; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getEntityRepository() { Chris@18: return $this->entityRepository; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@0: protected function getLanguageManager() { Chris@0: return $this->languageManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getView() { Chris@0: return $this->view; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function query(QueryPluginBase $query, $relationship = NULL) { Chris@0: $this->getEntityTranslationRenderer()->query($query, $relationship); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Renders entity field data. Chris@0: * Chris@0: * @param \Drupal\views\ResultRow $row Chris@0: * A single row of the query result. Chris@0: * @param \Drupal\views\Plugin\views\field\EntityField $field Chris@0: * (optional) A field to be rendered. Chris@0: * Chris@0: * @return array Chris@0: * A renderable array for the entity data contained in the result row. Chris@0: */ Chris@0: public function render(ResultRow $row, EntityField $field = NULL) { Chris@0: // The method is called for each field in each result row. In order to Chris@0: // leverage multiple-entity building of formatter output, we build the Chris@0: // render arrays for all fields in all rows on the first call. Chris@0: if (!isset($this->build)) { Chris@0: $this->build = $this->buildFields($this->view->result); Chris@0: } Chris@0: Chris@0: if (isset($field)) { Chris@0: $field_id = $field->options['id']; Chris@0: // Pick the render array for the row / field we are being asked to render, Chris@0: // and remove it from $this->build to free memory as we progress. Chris@0: if (isset($this->build[$row->index][$field_id])) { Chris@0: $build = $this->build[$row->index][$field_id]; Chris@0: unset($this->build[$row->index][$field_id]); Chris@0: } Chris@0: elseif (isset($this->build[$row->index])) { Chris@0: // In the uncommon case where a field gets rendered several times Chris@0: // (typically through direct Views API calls), the pre-computed render Chris@0: // array was removed by the unset() above. We have to manually rebuild Chris@0: // the render array for the row. Chris@0: $build = $this->buildFields([$row])[$row->index][$field_id]; Chris@0: } Chris@0: else { Chris@0: // In case the relationship is optional, there might not be any fields Chris@0: // to render for this row. Chris@0: $build = []; Chris@0: } Chris@0: } Chris@0: else { Chris@0: // Same logic as above, in the case where we are being called for a whole Chris@0: // row. Chris@0: if (isset($this->build[$row->index])) { Chris@0: $build = $this->build[$row->index]; Chris@0: unset($this->build[$row->index]); Chris@0: } Chris@0: else { Chris@0: $build = $this->buildFields([$row])[$row->index]; Chris@0: } Chris@0: } Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds the render arrays for all fields of all result rows. Chris@0: * Chris@0: * The output is built using EntityViewDisplay objects to leverage Chris@0: * multiple-entity building and ensure a common code path with regular entity Chris@0: * view. Chris@0: * - Each relationship is handled by a separate EntityFieldRenderer instance, Chris@0: * since it operates on its own set of entities. This also ensures different Chris@0: * entity types are handled separately, as they imply different Chris@0: * relationships. Chris@0: * - Within each relationship, the fields to render are arranged in unique Chris@0: * sets containing each field at most once (an EntityViewDisplay can Chris@0: * only process a field once with given display options, but a View can Chris@0: * contain the same field several times with different display options). Chris@0: * - For each set of fields, entities are processed by bundle, so that Chris@0: * formatters can operate on the proper field definition for the bundle. Chris@0: * Chris@0: * @param \Drupal\views\ResultRow[] $values Chris@0: * An array of all ResultRow objects returned from the query. Chris@0: * Chris@0: * @return array Chris@0: * A renderable array for the fields handled by this renderer. Chris@0: * Chris@0: * @see \Drupal\Core\Entity\Entity\EntityViewDisplay Chris@0: */ Chris@0: protected function buildFields(array $values) { Chris@0: $build = []; Chris@0: Chris@0: if ($values && ($field_ids = $this->getRenderableFieldIds())) { Chris@0: $entity_type_id = $this->getEntityTypeId(); Chris@0: Chris@0: // Collect the entities for the relationship, fetch the right translation, Chris@0: // and group by bundle. For each result row, the corresponding entity can Chris@0: // be obtained from any of the fields handlers, so we arbitrarily use the Chris@0: // first one. Chris@0: $entities_by_bundles = []; Chris@0: $field = $this->view->field[current($field_ids)]; Chris@0: foreach ($values as $result_row) { Chris@0: if ($entity = $field->getEntity($result_row)) { Chris@0: $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row); Chris@0: } Chris@0: } Chris@0: Chris@0: // Determine unique sets of fields that can be processed by the same Chris@0: // display. Fields that appear several times in the View open additional Chris@0: // "overflow" displays. Chris@0: $display_sets = []; Chris@0: foreach ($field_ids as $field_id) { Chris@0: $field = $this->view->field[$field_id]; Chris@0: $field_name = $field->definition['field_name']; Chris@0: $index = 0; Chris@0: while (isset($display_sets[$index]['field_names'][$field_name])) { Chris@0: $index++; Chris@0: } Chris@0: $display_sets[$index]['field_names'][$field_name] = $field; Chris@0: $display_sets[$index]['field_ids'][$field_id] = $field; Chris@0: } Chris@0: Chris@0: // For each set of fields, build the output by bundle. Chris@0: foreach ($display_sets as $display_fields) { Chris@0: foreach ($entities_by_bundles as $bundle => $bundle_entities) { Chris@0: // Create the display, and configure the field display options. Chris@0: $display = EntityViewDisplay::create([ Chris@0: 'targetEntityType' => $entity_type_id, Chris@0: 'bundle' => $bundle, Chris@0: 'status' => TRUE, Chris@0: ]); Chris@0: foreach ($display_fields['field_ids'] as $field) { Chris@0: $display->setComponent($field->definition['field_name'], [ Chris@0: 'type' => $field->options['type'], Chris@0: 'settings' => $field->options['settings'], Chris@0: ]); Chris@0: } Chris@0: // Let the display build the render array for the entities. Chris@0: $display_build = $display->buildMultiple($bundle_entities); Chris@0: // Collect the field render arrays and index them using our internal Chris@0: // row indexes and field IDs. Chris@0: foreach ($display_build as $row_index => $entity_build) { Chris@0: foreach ($display_fields['field_ids'] as $field_id => $field) { Chris@0: $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : []; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a list of names of entity fields to be rendered. Chris@0: * Chris@0: * @return string[] Chris@0: * An associative array of views fields. Chris@0: */ Chris@0: protected function getRenderableFieldIds() { Chris@0: $field_ids = []; Chris@0: foreach ($this->view->field as $field_id => $field) { Chris@0: if ($field instanceof EntityField && $field->relationship == $this->relationship) { Chris@0: $field_ids[] = $field_id; Chris@0: } Chris@0: } Chris@0: return $field_ids; Chris@0: } Chris@0: Chris@0: }