diff --git a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php index ab40e6f..e32753e 100644 --- a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php +++ b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php @@ -60,6 +60,7 @@ protected function setUp() { public function testCommentFieldName() { $view = Views::getView('test_comment_field_name'); $this->executeView($view); + $view->render(); $expected_result = [ [ @@ -84,6 +85,8 @@ public function testCommentFieldName() { $this->container->get('account_switcher')->switchTo(new AnonymousUserSession()); $view = Views::getView('test_comment_field_name'); $this->executeView($view); + $view->render(); + // Test that data rendered. $this->assertIdentical($this->comment->getFieldName(), $view->field['field_name']->advancedRender($view->result[0])); $this->assertIdentical($this->customComment->getFieldName(), $view->field['field_name']->advancedRender($view->result[1])); diff --git a/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php b/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php index 92ea637..3865667 100644 --- a/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php +++ b/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php @@ -137,6 +137,7 @@ public function _testSimpleFieldRender() { $view = Views::getView('test_view_fieldapi'); $this->prepareView($view); $this->executeView($view); + $view->render(); // Tests that the rendered fields match the actual value of the fields. for ($i = 0; $i < 3; $i++) { @@ -153,6 +154,7 @@ public function _testInaccessibleFieldRender() { $view = Views::getView('test_view_fieldapi'); $this->prepareView($view); $this->executeView($view); + $view->render(); // Check that the field handler for the hidden field is correctly removed // from the display. @@ -178,6 +180,7 @@ public function _testFormatterSimpleFieldRender() { 'trim_length' => 3, ); $this->executeView($view); + $view->render(); // Make sure that the formatter works as expected. // @TODO: actually there should be a specific formatter. @@ -196,6 +199,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE; $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); @@ -218,6 +222,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3; $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_offset'] = 1; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); @@ -238,6 +243,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3; $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_reversed'] = TRUE; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); @@ -259,6 +265,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_first_last'] = TRUE; $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_reversed'] = FALSE; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); @@ -277,6 +284,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['group_rows'] = TRUE; $view->displayHandlers->get('default')->options['fields'][$field_name]['separator'] = ':'; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); @@ -296,6 +304,7 @@ public function _testMultipleFieldRender() { $view->displayHandlers->get('default')->options['fields'][$field_name]['delta_limit'] = 3; $view->displayHandlers->get('default')->options['fields'][$field_name]['separator'] = '

test

'; $this->executeView($view); + $view->render(); for ($i = 0; $i < 3; $i++) { $rendered_field = $view->style_plugin->getField($i, $field_name); diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php index b48da1e..9488f84 100644 --- a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php +++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\RevisionableInterface; +use Drupal\Core\Field\FormatterPluginManager; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\views\Plugin\views\field\Field; use Drupal\views\Plugin\views\query\QueryPluginBase; @@ -32,7 +33,7 @@ class EntityFieldRenderer extends RendererBase { protected $entityManager; /** - * Static cache for ::getRenderableFieldNames(). + * Static cache for ::getRenderableFieldIds(). * * @var string[][] */ @@ -46,6 +47,13 @@ class EntityFieldRenderer extends RendererBase { protected $processedRows = []; /** + * The formatter plugin manager. + * + * @var \Drupal\Core\Field\FormatterPluginManager + */ + protected $formatterPluginManager; + + /** * Constructs a renderer object. * * @param \Drupal\views\ViewExecutable $view @@ -56,10 +64,13 @@ class EntityFieldRenderer extends RendererBase { * The entity type. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager + * The formatter plugin manager. */ - public function __construct(ViewExecutable $view, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) { + public function __construct(ViewExecutable $view, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager) { parent::__construct($view, $language_manager, $entity_type); $this->entityManager = $entity_manager; + $this->formatterPluginManager = $formatter_plugin_manager; } /** @@ -121,113 +132,159 @@ public function preRender(array $values) { * @see \Drupal\Core\Entity\Entity\EntityViewDisplay */ protected function buildFields(array $values) { - $entities = []; - $translations = []; - $bundle_field_names = []; - - foreach ($values as $row) { - // Skip this row if it has already been processed. - if (!empty($this->processedRows[$row->index])) { - continue; - } - $this->processedRows[$row->index] = TRUE; - - // Collect a list of entity types to process from the view relationships. - // We store a field name for each relationship to make it easier - // retrieving the related entity from the row. - if (!isset($entity_type_ids)) { - $row_entities = [$row->_entity] + array_intersect_key($row->_relationship_entities, $this->view->relationship); - $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 = isset($row->_relationship_entities[$rel_field_name]) ? $row->_relationship_entities[$rel_field_name] : $row->_entity; - $entity_type_id = $entity->getEntityTypeId(); - $bundle = $entity->bundle(); - $key = $this->getEntityRowKey($row, $entity); + if (isset($this->build)) { + return; + } - // Skip this entity if has already been processed. - if (isset($entities[$entity_type_id][$bundle][$key])) { - continue; - } + $build_list = []; - // We need to allow each field plugin to process the entity, but we - // clone it just once to improve performance. - $entity = $this->getEntityTranslation(clone $entity, $row); - $field_names = $this->getRenderableFieldNames(); - // There might be relationships without fields. A typical example is to - // show a list of nodes, filtered by the current user. - $field_names += [$entity_type_id => []]; - $processed = FALSE; - - 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]; - $processed_entity = $field->processEntity($row, $entity, FALSE); - if ($processed_entity) { - $entity = $processed_entity; - $processed = TRUE; - } - } - } + foreach ($this->getRenderableFieldIds() as $field_id) { + /** @var \Drupal\views\Plugin\views\field\Field $field */ + $field = $this->view->field[$field_id]; + $field_definition = $this->view->field[$field_id]->getFieldDefinition(); - // Collect entities and bundle information if the entity was processed, - // otherwise we have no data to render. - if ($processed) { - $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())); - } - } + $options = array( + 'field_definition' => $field_definition, + 'configuration' => array( + 'type' => $field->options['type'], + 'settings' => $field->options['settings'], + 'label' => 'hidden', + 'weight' => 0, + ), + 'view_mode' => '_custom', + ); + + /** @var \Drupal\Core\Entity\FieldableEntityInterface[] $entities */ + $entities = []; + foreach ($values as $index => $result_row) { + $entities[$index] = $this->getEntityTranslation(clone $field->getEntity($result_row), $result_row); } - } - // We need to prepare field output just once. - if ($entities) { - 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, EntityDisplayBase::CUSTOM_MODE); + if ($formatter = $this->formatterPluginManager->getInstance($options)) { + // Group items across all entities and pass them to the formatter's + // prepareView() method. + $grouped_items = array(); + foreach ($entities as $index => $entity) { + $items = $entity->get($this->view->field[$field_id]->definition['field_name']); + $items->filterEmptyItems(); + $grouped_items[$index] = $items; } + $formatter->prepareView($grouped_items); - // Build render arrays for each bundle. - if (!isset($this->build[$entity_type_id])) { - $this->build[$entity_type_id] = []; - } - foreach ($entity_type_entities as $bundle => $bundle_entities) { - $this->build[$entity_type_id] += $displays[$bundle]->buildMultiple($bundle_entities); + foreach ($this->view->result as $index => $result_row) { + // Then let the formatter build the output for each entity. + $items = $grouped_items[$index]; + $build_list[$index][$field_id] = $formatter->view($items); + $build_list[$index][$field_id]['#access'] = $items->access('view'); } } } + + $this->build = $build_list; + +// $entities = []; +// $translations = []; +// $bundle_field_names = []; +// +// foreach ($values as $row) { +// // Skip this row if it has already been processed. +// if (!empty($this->processedRows[$row->index])) { +// continue; +// } +// $this->processedRows[$row->index] = TRUE; +// +// // Collect a list of entity types to process from the view relationships. +// // We store a field name for each relationship to make it easier +// // retrieving the related entity from the row. +// if (!isset($entity_type_ids)) { +// $row_entities = [$row->_entity] + array_intersect_key($row->_relationship_entities, $this->view->relationship); +// $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 = isset($row->_relationship_entities[$rel_field_name]) ? $row->_relationship_entities[$rel_field_name] : $row->_entity; +// $entity_type_id = $entity->getEntityTypeId(); +// $bundle = $entity->bundle(); +// $key = $this->getEntityRowKey($row, $entity); +// +// // 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. +// $entity = $this->getEntityTranslation(clone $entity, $row); +// $field_names = $this->getRenderableFieldIds(); +// // There might be relationships without fields. A typical example is to +// // show a list of nodes, filtered by the current user. +// $field_names += [$entity_type_id => []]; +// $processed = FALSE; +// +// 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]; +// $processed_entity = $field->processEntity($row, $entity, FALSE); +// if ($processed_entity) { +// $entity = $processed_entity; +// $processed = TRUE; +// } +// } +// } +// +// // Collect entities and bundle information if the entity was processed, +// // otherwise we have no data to render. +// if ($processed) { +// $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())); +// } +// } +// } +// } +// +// // We need to prepare field output just once. +// if ($entities) { +// 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, EntityDisplayBase::CUSTOM_MODE); +// } +// +// // Build render arrays for each bundle. +// if (!isset($this->build[$entity_type_id])) { +// $this->build[$entity_type_id] = []; +// } +// foreach ($entity_type_entities as $bundle => $bundle_entities) { +// $this->build[$entity_type_id] += $displays[$bundle]->buildMultiple($bundle_entities); +// } +// } +// } } /** * Returns a list of names of entity fields to be rendered. * - * @return string[][] - * An associative array of entity field names keyed by the corresponding - * Views field plugin names. + * @return string[] + * An associative array of views fields. */ - protected function getRenderableFieldNames() { + protected function getRenderableFieldIds() { if (!isset($this->renderableFieldNames)) { $this->renderableFieldNames = []; foreach ($this->view->field as $view_field_name => $field) { - // Entity display does not support building the same field multiple - // times, thus we need to pick only the "primary" instance. - if ($field instanceof Field && $view_field_name == $field->definition['field_name']) { - $entity_type_id = $field->definition['entity_type']; - $this->renderableFieldNames[$entity_type_id][$view_field_name] = $view_field_name; + if ($field instanceof Field) { + $this->renderableFieldNames[] = $view_field_name; } } } @@ -255,47 +312,6 @@ protected function getEntityTranslation(EntityInterface $entity, ResultRow $row) } /** - * Returns the display objects to be used to render fields. - * - * @param string $entity_type_id - * The id of the entity type being processed. - * @param string[][] $bundle_field_names - * An associative array of arrays of field names keyed by bundle name. Each - * nested array is keyed by a Views field plugin name. - * - * @return \Drupal\Core\Entity\Entity\EntityViewDisplay[] - * An associative array of display objects keyed by bundle name. - */ - protected function getBundleDisplays($entity_type_id, $bundle_field_names) { - /** @var \Drupal\Core\Entity\Entity\EntityViewDisplay[] $displays */ - $displays = []; - $storage = $this->entityManager->getStorage('entity_view_display'); - - // Create one entity display object per bundle, so you can prepare entities - // in different bundles at the same time. - foreach ($bundle_field_names as $bundle => $field_names) { - $displays[$bundle] = $storage->create([ - 'targetEntityType' => $entity_type_id, - 'bundle' => $bundle, - 'viewMode' => EntityDisplayBase::CUSTOM_MODE, - ]); - - // Configure display options for the fields in the view. - foreach ($field_names as $view_field_name => $field_name) { - $field = $this->view->field[$view_field_name]; - $options = [ - 'type' => $field->options['type'], - 'settings' => $field->options['settings'], - 'label' => 'hidden', - ]; - $displays[$bundle]->setComponent($field_name, $options); - } - } - - return $displays; - } - - /** * Renders entity field data. * * @param \Drupal\views\ResultRow $row @@ -307,66 +323,19 @@ protected function getBundleDisplays($entity_type_id, $bundle_field_names) { * A renderable array for the entity data contained in the result row. */ public function render(ResultRow $row, Field $field = NULL) { - $build = $this->build; + if (!isset($this->build)) { + throw new \RuntimeException('Missing call to ::preRender(). You have to run $view->render() before rendering individual fields.'); + } + $build = []; if (isset($field)) { - /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ - $entity = $field->getEntity($row); - if (!$entity) { - $build = []; - } - else { - $entity_type_id = $entity->getEntityTypeId(); - $key = $this->getEntityRowKey($row, $entity); - $field_name = $field->definition['field_name']; - - // Make sure entity fields are actually rendered for the current row. - if (!isset($build[$entity_type_id][$key])) { - $this->buildFields([$row]); - $build = $this->build; - } - - // Use the pre-built array if available. In case of fields being - // rendered multiple times fall back to single-field rendering. - if (isset($build[$entity_type_id][$key][$field_name])) { - if ($field->field == $field_name) { - $build = $build[$entity_type_id][$key][$field_name]; - } - else { - $display = array( - 'type' => $field->options['type'], - 'settings' => $field->options['settings'], - 'label' => 'hidden', - ); - $build = $entity->get($field->definition['field_name'])->view($display); - } - } - else { - $build = []; - } + $field_id = $field->options['id']; + if (isset($this->build[$row->index][$field_id])) { + $build = $this->build[$row->index][$field_id]; } } return $build; } - /** - * Returns a unique identifier for a row entity. - * - * @param \Drupal\views\ResultRow $row - * A single row of the query result. - * @param \Drupal\Core\Entity\EntityInterface $entity - * The row entity. - * - * @return string - * The entity row key. - */ - protected function getEntityRowKey(ResultRow $row, EntityInterface $entity) { - $key = $row->index . ':' . $entity->id(); - if ($entity instanceof RevisionableInterface) { - $key .= ':' . $entity->getRevisionId(); - } - return $key; - } - } diff --git a/core/modules/views/src/Entity/Render/RendererBase.php b/core/modules/views/src/Entity/Render/RendererBase.php index ab64620..54b8f5a 100644 --- a/core/modules/views/src/Entity/Render/RendererBase.php +++ b/core/modules/views/src/Entity/Render/RendererBase.php @@ -45,7 +45,7 @@ * * @var array */ - protected $build = array(); + protected $build; /** * Constructs a renderer object. diff --git a/core/modules/views/src/FieldAPIHandlerTrait.php b/core/modules/views/src/FieldAPIHandlerTrait.php index 8a7fd12..48530cc 100644 --- a/core/modules/views/src/FieldAPIHandlerTrait.php +++ b/core/modules/views/src/FieldAPIHandlerTrait.php @@ -41,7 +41,7 @@ * @return \Drupal\Core\Field\FieldDefinitionInterface * The field definition used by this handler. */ - protected function getFieldDefinition() { + public function getFieldDefinition() { if (!$this->fieldDefinition) { $field_storage_config = $this->getFieldStorageDefinition(); $this->fieldDefinition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config); diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php index 22acc32..010106e 100644 --- a/core/modules/views/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -786,7 +786,7 @@ protected function getEntityFieldRenderer() { $base_table = $this->view->storage->get('base_table'); $views_data = $this->getViewsData()->get($base_table); $entity_type = $this->entityManager->getDefinition($views_data['table']['entity type']); - $this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->languageManager, $entity_type, $this->entityManager); + $this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->languageManager, $entity_type, $this->entityManager, $this->formatterPluginManager); } } return $this->entityFieldRenderer; diff --git a/core/modules/views/src/Tests/Handler/FieldFieldTest.php b/core/modules/views/src/Tests/Handler/FieldFieldTest.php index 47171c5..0496ef4 100644 --- a/core/modules/views/src/Tests/Handler/FieldFieldTest.php +++ b/core/modules/views/src/Tests/Handler/FieldFieldTest.php @@ -251,7 +251,7 @@ public function testSimpleExecute() { */ public function testSimpleRender() { $executable = Views::getView('test_field_field_test'); - $executable->execute(); + $executable->render(); $this->assertEqual(1, $executable->getStyle()->getField(0, 'id')); $this->assertEqual(3, $executable->getStyle()->getField(0, 'field_test')); diff --git a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php index 7ec98b8..9a6a6cc 100644 --- a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php +++ b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php @@ -77,6 +77,7 @@ public function testGroupRows() { // Test grouped rows. $this->executeView($view); + $view->render(); $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a, b, c'); // Change the group_rows checkbox to false. @@ -85,6 +86,8 @@ public function testGroupRows() { // Test ungrouped rows. $this->executeView($view); + $view->render(); + $view->row_index = 0; $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a'); $view->row_index = 1;