diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php index a701176..9fb7d74 100644 --- a/core/modules/views/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -127,12 +127,20 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt */ protected $renderField; + + /** + * A list of indexes of rows whose fields have already been rendered. + * + * @var int[] + */ + protected $processedRows = []; + /** * Stores the output of all rendered fields. * * @var array */ - protected $fieldOutput; + protected $fieldOutput = []; /** * Constructs a \Drupal\field\Plugin\views\field\Field object. @@ -232,13 +240,13 @@ public function getEntityTypeId() { protected function getEntityManager() { return $this->entityManager; } - /** * {@inheritdoc} */ protected function getLanguageManager() { return $this->languageManager; } + /** * {@inheritdoc} */ @@ -719,7 +727,6 @@ public function renderItems($items) { return $this->renderer->render($build); } } - /** * {@inheritdoc} */ @@ -744,19 +751,48 @@ protected function renderEntityFields(array $values) { $bundle_field_names = []; foreach ($values as $row) { - if (!isset($render_field->fieldOutput[$row->index]) && ($entity = $this->getEntity($row))) { + // Skip this row if has already been processed. + if (!empty($render_field->processedRows[$row->index])) { + continue; + } + $render_field->processedRows[$row->index] = TRUE; + + // Collect a list of entity types to process from the view relationships. + // We store a field name for each relationship toi make it easier + // retrieving the related entity from the row. + if (!isset($entity_type_ids)) { + $row_entities = array_intersect_key([$render_field->field => $row->_entity] + $row->_relationship_entities, $this->view->field); + $entity_type_ids = array_combine( + array_keys($row_entities), + array_map(function (EntityInterface $entity) { return $entity->getEntityTypeId(); }, $row_entities) + ); + } + + // Process all entities for each entity type. + foreach ($entity_type_ids as $rel_field_name => $entity_type_id) { + $entity = $this->view->field[$rel_field_name]->getEntity($row); + $entity_type_id = $entity->getEntityTypeId(); + $bundle = $entity->bundle(); + $key = $row->index . ':' . $entity->id(); + + // Skip this entity if has already been processed. + if (isset($entities[$entity_type_id][$bundle][$key])) { + continue; + } + // We need to allow each field plugin to process the entity, but we // clone it just once to improve performance. - /** @var \Drupal\Core\Entity\FieldableEntityInterface|\Drupal\Core\TypedData\TranslatableInterface $entity */ $entity = $this->getEntityTranslation(clone $entity, $row); $field_names = $this->getRenderableFieldNames(); $processed = FALSE; - foreach ($field_names as $view_field_name => $field_name) { + foreach ($field_names[$entity_type_id] as $view_field_name => $field_name) { if ($entity->hasField($field_name)) { /** @var \Drupal\views\Plugin\views\field\Field $field */ $field = $this->view->field[$view_field_name]; - if ($field->processEntity($row, $entity, FALSE)) { + $processed_entity = $field->processEntity($row, $entity, FALSE); + if ($processed_entity) { + $entity = $processed_entity; $processed = TRUE; } // @todo Initialize field item attributes. This is required to call @@ -772,11 +808,10 @@ protected function renderEntityFields(array $values) { // Collect entities and bundle information if the entity was processed, // otherwise we have no data to render. if ($processed) { - $entities[$row->index] = $entity; - $translations[$entity->language()->getId()][$entity->id()] = $entity; - $bundle = $entity->bundle(); - if (!isset($bundle_field_names[$bundle])) { - $bundle_field_names[$bundle] = array_intersect($field_names, array_keys($entity->getFieldDefinitions())); + $entities[$entity_type_id][$bundle][$key] = $entity; + $translations[$entity_type_id][$entity->language()->getId()][$entity->id()] = $entity; + if (!isset($bundle_field_names[$entity_type_id][$bundle])) { + $bundle_field_names[$entity_type_id][$bundle] = array_intersect($field_names[$entity_type_id], array_keys($entity->getFieldDefinitions())); } } } @@ -784,23 +819,24 @@ protected function renderEntityFields(array $values) { // We need to prepare field output just once. if ($entities) { - $entity_type_id = $this->getEntityType(); - $displays = $this->getBundleDisplays($entity_type_id, $bundle_field_names); - - // Run prepare_view on all result entities. We need to prepare entities - // per language as the array needs to be keyed by entity id. - foreach ($translations as $langcode => $entity_translations) { - $this->entityManager - ->getViewBuilder($entity_type_id) - ->prepareView($entity_translations, $displays, $this->getCustomViewMode()); - } + foreach ($entities as $entity_type_id => $entity_type_entities) { + $displays = $this->getBundleDisplays($entity_type_id, $bundle_field_names[$entity_type_id]); + + // Run prepare_view on all result entities. We need to prepare entities + // per language as the array needs to be keyed by entity id. + foreach ($translations[$entity_type_id] as $langcode => $entity_translations) { + $this->entityManager + ->getViewBuilder($entity_type_id) + ->prepareView($entity_translations, $displays, $this->getCustomViewMode($entity_type_id)); + } - // Build render arrays for each row. - foreach ($values as $row) { - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - $entity = $entities[$row->index]; - $bundle = $entity->bundle(); - $render_field->fieldOutput[$row->index] = $displays[$bundle]->build($entity); + // Build render arrays for each bundle. + if (!isset($render_field->fieldOutput[$entity_type_id])) { + $render_field->fieldOutput[$entity_type_id] = []; + } + foreach ($entity_type_entities as $bundle => $bundle_entities) { + $render_field->fieldOutput[$entity_type_id] += $displays[$bundle]->buildMultiple($bundle_entities); + } } } } @@ -808,7 +844,7 @@ protected function renderEntityFields(array $values) { /** * Returns a list of names of entity fields to be rendered. * - * @return string[] + * @return string[][] * An associative array of entity field names keyed by the corresponding * Views field plugin names. */ @@ -819,10 +855,17 @@ protected function getRenderableFieldNames() { // We use self here, so theoretically you could inherit this class for a // particular field. if ($field instanceof static) { - $field_name = $field->definition['field_name']; - $this->renderableFieldNames[$view_field_name] = $field_name; + $entity_type_id = $field->getFieldDefinition()->getTargetEntityTypeId(); + $this->renderableFieldNames[$entity_type_id][$view_field_name] = $field->definition['field_name']; + if (!isset($this->renderField) && $field->options['relationship'] == 'none') { + $this->renderField = $field; + } } } + // Fall back to the current field if could not find any better one. + if (!isset($this->renderField)) { + $this->renderField = $this; + } } return $this->renderableFieldNames; } @@ -835,8 +878,7 @@ protected function getRenderableFieldNames() { */ public function getRenderField() { if (!isset($this->renderField)) { - $name = key($this->getRenderableFieldNames()); - $this->renderField = $this->view->field[$name]; + $this->getRenderableFieldNames(); } return $this->renderField; } @@ -844,11 +886,14 @@ public function getRenderField() { /** * Returns the custom view mode to be used for field rendering. * + * @param string $entity_type_id + * The type of the entities whose fields are being rendered. + * * @return string * A custom view mode name. */ - protected function getCustomViewMode() { - return 'views:' . $this->view->storage->id() . ':' . $this->view->current_display; + protected function getCustomViewMode($entity_type_id) { + return 'views:' . $this->view->storage->id() . ':' . $this->view->current_display . ':' . $entity_type_id; } /** @@ -867,7 +912,7 @@ protected function getBundleDisplays($entity_type_id, $bundle_field_names) { /** @var \Drupal\Core\Entity\Entity\EntityViewDisplay[] $displays */ $displays = []; $storage = $this->entityManager->getStorage('entity_view_display'); - $view_mode = $this->getCustomViewMode(); + $view_mode = $this->getCustomViewMode($entity_type_id); // Create on entity display object per bundle, so you can prepare entities // in different bundles at the same time. @@ -914,19 +959,21 @@ public function getFieldOutput() { public function getItems(ResultRow $values) { $entity = $this->getEntity($values); $output = $this->getFieldOutput(); + $entity_type_id = $entity->getEntityTypeId(); + $key = $values->index . ':' . $entity->id(); $field_name = $this->definition['field_name']; // Make sure entity fields are actually rendered for the current row. - if (!isset($output[$values->index])) { + if (!isset($output[$entity_type_id][$key])) { $this->renderEntityFields([$values]); $output = $this->getFieldOutput(); } - if (!$entity || !isset($output[$values->index][$field_name])) { + if (!$entity || !isset($output[$entity_type_id][$key][$field_name])) { return []; } - $build = $output[$values->index][$field_name]; + $build = $output[$entity_type_id][$key][$field_name]; if ($this->options['field_api_classes']) { return [['rendered' => $this->renderer->render($build)]]; } @@ -1104,8 +1151,8 @@ protected function addSelfTokens(&$tokens, $item) { * @param \Drupal\views\ResultRow $row * The result row the field value being processed belongs to. * - * @return string - * The field language code. + * @return \Drupal\Core\Entity\FieldableEntityInterface + * The entity translation object for the passed row. */ protected function getEntityTranslation(EntityInterface $entity, ResultRow $row) { // Even if the current field is not attached to the main entity, we use it