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;