diff --git a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
index 3771d81..8c7433c 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
@@ -8,6 +8,7 @@
namespace Drupal\Core\Entity\Display;
use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Provides a common interface for entity view displays.
@@ -51,4 +52,39 @@ public function build(FieldableEntityInterface $entity);
*/
public function buildMultiple(array $entities);
+ /**
+ * Returns a renderable array for the specified field.
+ *
+ * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+ * The entity being displayed.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The definition of the field to be displayed.
+ * @param string $type
+ * The formatter type.
+ * @param array $settings
+ * The formatter settings.
+ *
+ * @return array
+ * A renderable array for the field.
+ */
+ public function buildField(FieldableEntityInterface $entity, FieldDefinitionInterface $field_definition, $type, $settings);
+
+ /**
+ * Returns a renderable array for the specified field of a set of entities.
+ *
+ * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
+ * The entities being displayed.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The definition of the field to be displayed.
+ * @param string $type
+ * The formatter type.
+ * @param array $settings
+ * The formatter settings.
+ *
+ * @return array
+ * A renderable array for the field, indexed by the same keys as the
+ * $entities array parameter.
+ */
+ public function buildFieldMultiple(array $entities, FieldDefinitionInterface $field_definition, $type, $settings);
+
}
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
index efffdbc..c9a92b4 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
@@ -9,10 +9,12 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Entity\EntityDisplayPluginCollection;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
-use Drupal\Core\Entity\EntityDisplayBase;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FormatterInterface;
/**
* Configuration entity that contains display options for all components of a
@@ -225,22 +227,7 @@ public function buildMultiple(array $entities) {
// Run field formatters.
foreach ($this->getComponents() as $name => $options) {
if ($formatter = $this->getRenderer($name)) {
- // Group items across all entities and pass them to the formatter's
- // prepareView() method.
- $grouped_items = array();
- foreach ($entities as $id => $entity) {
- $items = $entity->get($name);
- $items->filterEmptyItems();
- $grouped_items[$id] = $items;
- }
- $formatter->prepareView($grouped_items);
-
- // Then let the formatter build the output for each entity.
- foreach ($entities as $id => $entity) {
- $items = $grouped_items[$id];
- $build_list[$id][$name] = $formatter->view($items);
- $build_list[$id][$name]['#access'] = $items->access('view');
- }
+ $this->formatFieldMultiple($build_list, $entities, $formatter, $name);
}
}
@@ -265,6 +252,87 @@ public function buildMultiple(array $entities) {
}
/**
+ * Formats a field for multiple entities.
+ *
+ * @param array &$build_list
+ * A reference to a renderable array, indexed by the same keys as the
+ * $entities array parameter.
+ * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
+ * The entities being displayed.
+ * @param \Drupal\Core\Field\FormatterInterface $formatter
+ * The field formatter to be used.
+ * @param string $field_name
+ * The name of the field to be formatted.
+ * @param string $field_key
+ * (optional) The key every field render array will be keyed with in the
+ * build list. Defaults to the field name.
+ */
+ protected function formatFieldMultiple(array &$build_list, array $entities, FormatterInterface $formatter, $field_name, $field_key = NULL) {
+ if (!isset($field_key)) {
+ $field_key = $field_name;
+ }
+
+ // 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($field_name);
+ $items->filterEmptyItems();
+ $grouped_items[$index] = $items;
+ }
+ $formatter->prepareView($grouped_items);
+
+ foreach ($entities as $index => $entity) {
+ /** @var \Drupal\Core\Field\FieldItemListInterface $items */
+ $items = $grouped_items[$index];
+ // If a field key is explicitly not specified, it means we do not need the
+ // additional nesting level.
+ if ($field_key) {
+ $build = &$build_list[$index][$field_key];
+ }
+ else {
+ $build = &$build_list[$index];
+ }
+ // Then let the formatter build the output for each entity.
+ $build = $formatter->view($items);
+ $build['#access'] = $items->access('view');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildField(FieldableEntityInterface $entity, FieldDefinitionInterface $field_definition, $type, $settings) {
+ $build_list = $this->buildFieldMultiple([$entity], $field_definition, $type, $settings);
+ return reset($build_list);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildFieldMultiple(array $entities, FieldDefinitionInterface $field_definition, $type, $settings) {
+ $build_list = [];
+
+ $options = [
+ 'field_definition' => $field_definition,
+ 'configuration' => array(
+ 'type' => $type,
+ 'settings' => $settings,
+ 'label' => 'hidden',
+ 'weight' => 0,
+ ),
+ 'view_mode' => static::CUSTOM_MODE,
+ ];
+
+ /** @var \Drupal\Core\Field\FormatterInterface $formatter */
+ if ($formatter = $this->pluginManager->getInstance($options)) {
+ $this->formatFieldMultiple($build_list, $entities, $formatter, $field_definition->getName(), FALSE);
+ }
+
+ return $build_list;
+ }
+
+ /**
* {@inheritdoc}
*/
public function getPluginCollections() {
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 352679c..40eaeeb 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -855,7 +855,7 @@ public function getEntityTypeLabels($group = FALSE) {
public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array()) {
$translation = $entity;
- if ($entity instanceof TranslatableInterface) {
+ if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
if (empty($langcode)) {
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
}
diff --git a/core/lib/Drupal/Core/Field/FormatterInterface.php b/core/lib/Drupal/Core/Field/FormatterInterface.php
index 4ba0efa..cc915fe 100644
--- a/core/lib/Drupal/Core/Field/FormatterInterface.php
+++ b/core/lib/Drupal/Core/Field/FormatterInterface.php
@@ -60,7 +60,7 @@ public function settingsSummary();
* items.
*
* @param \Drupal\Core\Field\FieldItemListInterface[] $entities_items
- * Array of field values, keyed by entity ID.
+ * An array of field item values.
*/
public function prepareView(array $entities_items);
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 92353f0..aeb67f5 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);
@@ -205,7 +209,7 @@ public function _testMultipleFieldRender() {
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
- $this->assertEqual($rendered_field, implode(', ', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited.');
}
// Test that an empty field is rendered without error.
@@ -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);
@@ -227,7 +232,7 @@ public function _testMultipleFieldRender() {
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
- $this->assertEqual($rendered_field, implode(', ', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited and the offset is correct.');
}
$view->destroy();
@@ -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);
@@ -248,7 +254,7 @@ public function _testMultipleFieldRender() {
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
- $this->assertEqual($rendered_field, implode(', ', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode(', ', $items), 'The amount of items is limited and they are reversed.');
}
$view->destroy();
@@ -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);
@@ -266,7 +273,7 @@ public function _testMultipleFieldRender() {
$pure_items = $this->nodes[$i]->{$field_name}->getValue();
$items[] = $pure_items[0]['value'];
$items[] = $pure_items[4]['value'];
- $this->assertEqual($rendered_field, implode(', ', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode(', ', $items), 'Items are limited to first and last.');
}
$view->destroy();
@@ -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);
@@ -286,7 +294,7 @@ public function _testMultipleFieldRender() {
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
- $this->assertEqual($rendered_field, implode(':', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode(':', $items), 'The amount of items is limited and the custom separator is correct.');
}
$view->destroy();
@@ -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);
@@ -305,7 +314,7 @@ public function _testMultipleFieldRender() {
foreach ($pure_items as $j => $item) {
$items[] = $pure_items[$j]['value'];
}
- $this->assertEqual($rendered_field, implode('test
', $items), 'Make sure that the amount of items is limited.');
+ $this->assertEqual($rendered_field, implode('test
', $items), 'The custom separator is correctly escaped.');
}
$view->destroy();
}
diff --git a/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php b/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
index 75623f4..91c8532 100644
--- a/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
+++ b/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
@@ -72,6 +72,7 @@ public function testFileExtensionTarOption() {
$view = Views::getView('file_extension_view');
$view->setDisplay();
$this->executeView($view);
+ $view->render();
// Test without the tar option.
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
@@ -86,6 +87,7 @@ public function testFileExtensionTarOption() {
$view->field['extension']->options['settings']['extension_detect_tar'] = TRUE;
$this->executeView($view);
+ $view->render();
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
$this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index f179ec8..824c571 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -216,6 +216,11 @@ function rdf_entity_prepare_view($entity_type, array $entities, array $displays)
$field_mapping = $mapping->getPreparedFieldMapping($name);
if ($field_mapping) {
foreach ($entity->get($name) as $item) {
+ // @todo Initialize field item attributes, if they are missing. Remove
+ // this in https://www.drupal.org/node/2450477.
+ if (!isset($item->_attributes)) {
+ $item->_attributes = array();
+ }
$item->_attributes += rdf_rdfa_attributes($field_mapping, $item->toArray());
}
}
diff --git a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
index c8549e6..7d513eb 100644
--- a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
@@ -383,10 +383,20 @@ protected function doTestEntityTranslationAPI($entity_type) {
// Verify that changing the default translation flag causes an exception to
// be thrown.
- $message = 'The default translation flag cannot be changed.';
foreach ($entity->getTranslationLanguages() as $t_langcode => $language) {
$translation = $entity->getTranslation($t_langcode);
$default = $translation->isDefaultTranslation();
+
+ $message = 'The default translation flag can be reassigned the same value.';
+ try {
+ $translation->{$default_langcode_key}->value = $default;
+ $this->pass($message);
+ }
+ catch (\LogicException $e) {
+ $this->fail($message);
+ }
+
+ $message = 'The default translation flag cannot be changed.';
try {
$translation->{$default_langcode_key}->value = !$default;
$this->fail($message);
@@ -394,6 +404,7 @@ protected function doTestEntityTranslationAPI($entity_type) {
catch (\LogicException $e) {
$this->pass($message);
}
+
$this->assertEqual($translation->{$default_langcode_key}->value, $default);
}
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.views.inc b/core/modules/system/tests/modules/entity_test/entity_test.views.inc
new file mode 100644
index 0000000..d74af47
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/entity_test.views.inc
@@ -0,0 +1,9 @@
+field['name']->options['link_to_user'] = TRUE;
$view->field['name']->options['type'] = 'user_name';
$view->field['name']->init($view, $view->getDisplay('default'));
+ $view->field['name']->options['id'] = 'name';
$this->executeView($view);
$anon_name = $this->config('user.settings')->get('anonymous');
diff --git a/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php b/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
index 8470efd..697ae8a 100644
--- a/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
@@ -15,7 +15,7 @@
/**
* Renders entities in a configured language.
*/
-class ConfigurableLanguageRenderer extends RendererBase {
+class ConfigurableLanguageRenderer extends EntityTranslationRendererBase {
/**
* A specific language code for rendering if available.
diff --git a/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php b/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
index bab75d8..dbd3a72 100644
--- a/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
@@ -12,7 +12,7 @@
/**
* Renders entities in their default language.
*/
-class DefaultLanguageRenderer extends RendererBase {
+class DefaultLanguageRenderer extends EntityTranslationRendererBase {
/**
* {@inheritdoc}
diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
new file mode 100644
index 0000000..7dc07b9
--- /dev/null
+++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
@@ -0,0 +1,272 @@
+entityManager = $entity_manager;
+ $this->formatterPluginManager = $formatter_plugin_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheContexts() {
+ return $this->getEntityTranslationRenderer()->getCacheContexts();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEntityTypeId() {
+ return $this->entityType->id();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntityManager() {
+ return $this->entityManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+
+ protected function getLanguageManager() {
+ return $this->languageManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getView() {
+ return $this->view;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query(QueryPluginBase $query, $relationship = NULL) {
+ $this->getEntityTranslationRenderer()->query($query, $relationship);
+ }
+
+ /**
+ * Renders all fields of all rows using entity display objects.
+ *
+ * @param \Drupal\views\ResultRow[] $values
+ * An array of all ResultRow objects returned from the query.
+ *
+ * @see \Drupal\Core\Entity\Entity\EntityViewDisplay
+ */
+ protected function buildFields(array $values) {
+ $build = [];
+ if ($renderable_fields = $this->getRenderableFieldIds()) {
+ // Identify all relationships. We will also need one representative field
+ // for each relationship.
+ $relationhips = [];
+ foreach ($renderable_fields as $field_id) {
+ /** @var \Drupal\views\Plugin\views\field\Field $field */
+ $field = $this->view->field[$field_id];
+ $relationhips[$field->options['relationship']] = $field;
+ }
+
+ // For each row, and each relationship in the row, retrieve the entity to
+ // be processed, and take the correct translation.
+ $entities = [];
+ foreach ($values as $result_row) {
+ foreach ($relationhips as $relationhip_id => $field) {
+ $entity = $field->getEntity($result_row);
+ $entities[$relationhip_id][$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
+ }
+ }
+
+ // @todo Note : given the current code in EVD->buildFieldMultiple(), we don't
+ // actually need to create an EVD for each entity_type / bundle. This shows
+ // that EVD->buildFieldMultiple() should rather go in a stateless service,
+ // since it does in fact not depend on the values in the EVD.
+ $display = new EntityViewDisplay([
+ 'targetEntityType' => $this->getEntityTypeId(),
+ 'bundle' => FALSE,
+ 'status' => TRUE,
+ ], 'entity_view_display');
+
+ // Render each field
+ foreach ($renderable_fields as $field_id) {
+ /** @var \Drupal\views\Plugin\views\field\Field $field */
+ $field = $this->view->field[$field_id];
+
+ // Run the formatter across all entities for the field's relationship,
+ // grouping entities by bundle.
+ foreach ($entities[$field->options['relationship']] as $bundle => $bundle_entities) {
+ // At this point, we can use the "real" field definition for the bundle
+ // we are rendering.
+ $field_definition = $this->entityManager->getFieldDefinitions($this->getEntityTypeId(), $bundle)[$field->definition['field_name']];
+ $field_build = $display->buildFieldMultiple($bundle_entities, $field_definition, $field->options['type'], $field->options['settings']);
+ array_walk($field_build, function ($value, $index) use (&$build, $field_id) {
+ $build[$index][$field_id] = $value;
+ });
+ }
+ }
+ }
+
+ return $build;
+ }
+
+ /**
+ * Returns a list of names of entity fields to be rendered.
+ *
+ * @return string[]
+ * An associative array of views fields.
+ */
+ protected function getRenderableFieldIds() {
+ if (!isset($this->renderableFieldIds)) {
+ $this->renderableFieldIds = [];
+ foreach ($this->view->field as $field_id => $field) {
+ if ($field instanceof Field && $field->getEntityType() == $this->getEntityTypeId()) {
+ $this->renderableFieldIds[] = $field_id;
+ }
+ }
+ }
+ return $this->renderableFieldIds;
+ }
+
+ /**
+ * Return the code of the language the field should be displayed in.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity object the field value being processed is attached to.
+ * @param \Drupal\views\ResultRow $row
+ * The result row the field value being processed belongs to.
+ *
+ * @return \Drupal\Core\Entity\FieldableEntityInterface
+ * The entity translation object for the passed row.
+ */
+ protected function getEntityTranslation(EntityInterface $entity, ResultRow $row) {
+ // We assume the same language should be used for all entity fields
+ // belonging to a single row, even if they are attached to different entity
+ // types. Below we apply language fallback to ensure a valid value is always
+ // picked.
+ $langcode = $this->getEntityTranslationRenderer()->getLangcode($row);
+ return $this->entityManager->getTranslationFromContext($entity, $langcode);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function preRender(array $result) {
+ // For efficient preRender() populated $this->build with the render arrays for all fields
+ // in all rows
+ $this->build = $this->buildFields($result);
+ }
+
+ /**
+ * Renders entity field data.
+ *
+ * @param \Drupal\views\ResultRow $row
+ * A single row of the query result.
+ * @param \Drupal\views\Plugin\views\field\Field $field
+ * (optional) A field to be rendered.
+ *
+ * @return array
+ * A renderable array for the entity data contained in the result row.
+ */
+ public function render(ResultRow $row, Field $field = NULL) {
+ if (isset($field)) {
+ $field_id = $field->options['id'];
+ // preRender() initializes $this->build with the render arrays for all
+ // fields across all rows. We simply pick the one for the row / field we
+ // are being asked to render, and remove it from $this->build to free
+ // memory as we progress.
+ if (isset($this->build[$row->index][$field_id])) {
+ $build = $this->build[$row->index][$field_id];
+ unset($this->build[$row->index][$field_id]);
+ }
+ else {
+ // In the uncommon case where a field gets rendered several times
+ // (typically through direct Views API calls), the pre-computed render
+ // array was removed be the unset() above. We the manually rebuild the
+ // render array for the row.
+ $build = $this->buildFields([$row])[$row->index][$field_id];
+ }
+ }
+ else {
+ // Same logic as above, in the case where we are being called for a whole
+ // row.
+ if (isset($this->build[$row->index])) {
+ $build = $this->build[$row->index];
+ unset($this->build[$row->index]);
+ }
+ else {
+ $build = $this->buildFields([$row])[$row->index];
+ }
+ }
+
+ return $build;
+ }
+
+}
diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
index 1be7364..f874c75 100644
--- a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
+++ b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
@@ -10,25 +10,25 @@
use Drupal\views\Plugin\views\PluginBase;
/**
- * Trait used to instantiate the view's entity language render.
+ * Trait used to instantiate the view's entity translation renderer.
*/
trait EntityTranslationRenderTrait {
/**
* The renderer to be used to render the entity row.
*
- * @var \Drupal\views\Entity\Render\RendererBase
+ * @var \Drupal\views\Entity\Render\EntityTranslationRendererBase
*/
- protected $entityLanguageRenderer;
+ protected $entityTranslationRenderer;
/**
* Returns the current renderer.
*
- * @return \Drupal\views\Entity\Render\RendererBase
+ * @return \Drupal\views\Entity\Render\EntityTranslationRendererBase
* The configured renderer.
*/
protected function getEntityTranslationRenderer() {
- if (!isset($this->entityLanguageRenderer)) {
+ if (!isset($this->entityTranslationRenderer)) {
$view = $this->getView();
$rendering_language = $view->display_handler->getOption('rendering_language');
$langcode = NULL;
@@ -52,9 +52,9 @@ protected function getEntityTranslationRenderer() {
}
$class = '\Drupal\views\Entity\Render\\' . $renderer;
$entity_type = $this->getEntityManager()->getDefinition($this->getEntityTypeId());
- $this->entityLanguageRenderer = new $class($view, $this->getLanguageManager(), $entity_type, $langcode);
+ $this->entityTranslationRenderer = new $class($view, $this->getLanguageManager(), $entity_type, $langcode);
}
- return $this->entityLanguageRenderer;
+ return $this->entityTranslationRenderer;
}
/**
diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php
new file mode 100644
index 0000000..9ceb84e
--- /dev/null
+++ b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php
@@ -0,0 +1,64 @@
+view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
+
+ /** @var \Drupal\views\ResultRow $row */
+ foreach ($result as $row) {
+ $entity = $row->_entity;
+ $entity->view = $this->view;
+ $this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render(ResultRow $row) {
+ $entity_id = $row->_entity->id();
+ return $this->build[$entity_id];
+ }
+
+}
diff --git a/core/modules/views/src/Entity/Render/RendererBase.php b/core/modules/views/src/Entity/Render/RendererBase.php
index a0baec6..9bbb7e7 100644
--- a/core/modules/views/src/Entity/Render/RendererBase.php
+++ b/core/modules/views/src/Entity/Render/RendererBase.php
@@ -8,7 +8,6 @@
namespace Drupal\views\Entity\Render;
use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
@@ -16,7 +15,7 @@
use Drupal\views\ViewExecutable;
/**
- * Defines a base class for entity row renderers.
+ * Defines a base class for entity renderers.
*/
abstract class RendererBase implements CacheablePluginInterface {
@@ -46,7 +45,7 @@
*
* @var array
*/
- protected $build = array();
+ protected $build;
/**
* Constructs a renderer object.
@@ -72,24 +71,6 @@ public function isCacheable() {
}
/**
- * {@inheritdoc}
- */
- public function getCacheContexts() {
- return [];
- }
-
- /**
- * Returns the language code associated to the given row.
- *
- * @param \Drupal\views\ResultRow $row
- * The result row.
- *
- * @return string
- * A language code.
- */
- abstract public function getLangcode(ResultRow $row);
-
- /**
* Alters the query if needed.
*
* @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
@@ -97,38 +78,26 @@ public function getCacheContexts() {
* @param string $relationship
* (optional) The relationship, used by a field.
*/
- public function query(QueryPluginBase $query, $relationship = NULL) {
- }
+ abstract public function query(QueryPluginBase $query, $relationship = NULL);
/**
- * Runs before each row is rendered.
+ * Runs before each entity is rendered.
*
* @param $result
* The full array of results from the query.
*/
public function preRender(array $result) {
- $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
-
- /** @var \Drupal\views\ResultRow $row */
- foreach ($result as $row) {
- $entity = $row->_entity;
- $entity->view = $this->view;
- $this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
- }
}
/**
- * Renders a row object.
+ * Renders entity data.
*
* @param \Drupal\views\ResultRow $row
* A single row of the query result.
*
* @return array
- * The renderable array of a single row.
+ * A renderable array for the entity data contained in the result row.
*/
- public function render(ResultRow $row) {
- $entity_id = $row->_entity->id();
- return $this->build[$entity_id];
- }
+ abstract public function render(ResultRow $row);
}
diff --git a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
index cf0c296..05e5883 100644
--- a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
@@ -14,7 +14,7 @@
/**
* Renders entity translations in their active language.
*/
-class TranslationLanguageRenderer extends RendererBase {
+class TranslationLanguageRenderer extends EntityTranslationRendererBase {
/**
* Stores the field alias of the langcode column.
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 66dbdeb..14e1218 100644
--- a/core/modules/views/src/Plugin/views/field/Field.php
+++ b/core/modules/views/src/Plugin/views/field/Field.php
@@ -16,13 +16,12 @@
use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\FieldAPIHandlerTrait;
-use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
+use Drupal\views\Entity\Render\EntityFieldRenderer;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ResultRow;
@@ -30,7 +29,7 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
- * A field that displays fieldapi fields.
+ * A field that displays entity field data.
*
* @ingroup views_field_handlers
*
@@ -39,8 +38,6 @@
* @ViewsField("field")
*/
class Field extends FieldPluginBase implements CacheablePluginInterface, MultiItemsFieldHandlerInterface {
- use EntityTranslationRenderTrait;
-
use FieldAPIHandlerTrait;
/**
@@ -114,6 +111,13 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
protected $fieldTypePluginManager;
/**
+ * Static cache for ::getEntityFieldRenderer().
+ *
+ * @var \Drupal\views\Entity\Render\EntityFieldRenderer
+ */
+ protected $entityFieldRenderer;
+
+ /**
* Constructs a \Drupal\field\Plugin\views\field\Field object.
*
* @param array $configuration
@@ -201,13 +205,6 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
/**
* {@inheritdoc}
*/
- public function getEntityTypeId() {
- return $this->getEntityType();
- }
-
- /**
- * {@inheritdoc}
- */
protected function getEntityManager() {
return $this->entityManager;
}
@@ -215,19 +212,6 @@ protected function getEntityManager() {
/**
* {@inheritdoc}
*/
- protected function getLanguageManager() {
- return $this->languageManager;
- }
- /**
- * {@inheritdoc}
- */
- protected function getView() {
- return $this->view;
- }
-
- /**
- * {@inheritdoc}
- */
public function access(AccountInterface $account) {
$access_control_handler = $this->entityManager->getAccessControlHandler($this->getEntityType());
return $access_control_handler->fieldAccess('view', $this->getFieldDefinition(), $account);
@@ -271,8 +255,8 @@ public function query($use_groupby = FALSE) {
$this->addAdditionalFields($fields);
}
- // Let the configured entity translation renderer alter the query if needed.
- $this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
+ // Let the entity field renderer alter the query if needed.
+ $this->getEntityFieldRenderer()->query($this->query, $this->relationship);
}
/**
@@ -685,6 +669,7 @@ public function submitGroupByForm(&$form, FormStateInterface $form_state) {
*/
public function renderItems($items) {
if (!empty($items)) {
+ $items = $this->prepareItemsByDelta($items);
if ($this->options['multi_type'] == 'separator' || !$this->options['group_rows']) {
$separator = $this->options['multi_type'] == 'separator' ? SafeMarkup::checkAdminXss($this->options['separator']) : '';
$build = [
@@ -706,6 +691,107 @@ public function renderItems($items) {
}
/**
+ * Adapts the $items according to the delta configuration.
+ *
+ * The method filters limits the shown deltas, reorders the items as well as
+ * takens into account an offset.
+ *
+ * @param array $all_values
+ * The items for individual rendering.
+ *
+ * @return array
+ * The manipulated items.
+ */
+ protected function prepareItemsByDelta(array $all_values) {
+ if ($this->options['delta_reversed']) {
+ $all_values = array_reverse($all_values);
+ }
+
+ // We are supposed to show only certain deltas.
+ if ($this->limit_values) {
+ $row = $this->view->result[$this->view->row_index];
+
+ // Offset is calculated differently when row grouping for a field is
+ // not enabled. Since there are multiple rows, the delta needs to be
+ // taken into account, so that different values are shown per row.
+ if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($row->{$this->aliases['delta']})) {
+ $delta_limit = 1;
+ $offset = $row->{$this->aliases['delta']};
+ }
+ // Single fields don't have a delta available so choose 0.
+ elseif (!$this->options['group_rows'] && !$this->multiple) {
+ $delta_limit = 1;
+ $offset = 0;
+ }
+ else {
+ $delta_limit = $this->options['delta_limit'];
+ $offset = intval($this->options['delta_offset']);
+
+ // We should only get here in this case if there's an offset, and
+ // in that case we're limiting to all values after the offset.
+ if ($delta_limit === 0) {
+ $delta_limit = count($all_values) - $offset;
+ }
+ }
+
+ // Determine if only the first and last values should be shown
+ $delta_first_last = $this->options['delta_first_last'];
+
+ $new_values = array();
+ for ($i = 0; $i < $delta_limit; $i++) {
+ $new_delta = $offset + $i;
+
+ if (isset($all_values[$new_delta])) {
+ // If first-last option was selected, only use the first and last values
+ if (!$delta_first_last
+ // Use the first value.
+ || $new_delta == $offset
+ // Use the last value.
+ || $new_delta == ($delta_limit + $offset - 1)) {
+ $new_values[] = $all_values[$new_delta];
+ }
+ }
+ }
+ $all_values = $new_values;
+ }
+
+ return $all_values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function preRender(&$values) {
+ parent::preRender($values);
+ $this->getEntityFieldRenderer()->preRender($values);
+ }
+
+ /**
+ * Returns the entity field renderer.
+ *
+ * @return \Drupal\views\Entity\Render\EntityFieldRenderer
+ * The entity field renderer.
+ */
+ protected function getEntityFieldRenderer() {
+ $entity_type_id = $this->getEntityType();
+ if (!isset($this->entityFieldRenderer[$entity_type_id])) {
+ if (!empty($this->view->field)) {
+ foreach ($this->view->field as $field) {
+ if ($field instanceof Field && isset($field->entityFieldRenderer[$entity_type_id])) {
+ $this->entityFieldRenderer[$entity_type_id] = $field->entityFieldRenderer[$entity_type_id];
+ break;
+ }
+ }
+ }
+ if (!isset($this->entityFieldRenderer[$entity_type_id])) {
+ $entity_type = $this->entityManager->getDefinition($entity_type_id);
+ $this->entityFieldRenderer[$entity_type_id] = new EntityFieldRenderer($this->view, $this->languageManager, $entity_type, $this->entityManager, $this->formatterPluginManager);
+ }
+ }
+ return $this->entityFieldRenderer[$entity_type_id];
+ }
+
+ /**
* Gets an array of items for the field.
*
* @param \Drupal\views\ResultRow $values
@@ -715,41 +801,31 @@ public function renderItems($items) {
* An array of items for the field.
*/
public function getItems(ResultRow $values) {
- $original_entity = $this->getEntity($values);
- if (!$original_entity) {
- return array();
- }
- $entity = $this->process_entity($values, $original_entity);
- if (!$entity) {
- return array();
- }
+ $build = $this->getEntityFieldRenderer()->render($values, $this);
- $display = array(
- 'type' => $this->options['type'],
- 'settings' => $this->options['settings'],
- 'label' => 'hidden',
- );
- $render_array = $entity->get($this->definition['field_name'])->view($display);
-
- $items = array();
+ if (!$build) {
+ return [];
+ }
if ($this->options['field_api_classes']) {
- return array(array('rendered' => $this->renderer->render($render_array)));
+ return [['rendered' => $this->renderer->render($build)]];
}
- foreach (Element::children($render_array) as $count) {
- $items[$count]['rendered'] = $render_array[$count];
- // FieldItemListInterface::view() adds an #access property to the render
- // array that determines whether or not the current user is allowed to
- // view the field in the context of the current entity. We need to respect
- // this parameter when we pull out the children of the field array for
+ // Render using the formatted data itself.
+ $items = [];
+ foreach (Element::children($build) as $delta) {
+ $items[$delta]['rendered'] = $build[$delta];
+ // EntityViewDisplay::build() adds an #access property to the render array
+ // that determines whether or not the current user is allowed to view the
+ // field in the context of the current entity. We need to respect this
+ // parameter when we pull out the children of the field array for
// rendering.
- if (isset($render_array['#access'])) {
- $items[$count]['rendered']['#access'] = $render_array['#access'];
+ if (isset($build['#access'])) {
+ $items[$delta]['rendered']['#access'] = $build['#access'];
}
// Only add the raw field items (for use in tokens) if the current user
// has access to view the field content.
- if ((!isset($items[$count]['rendered']['#access']) || $items[$count]['rendered']['#access']) && !empty($render_array['#items'][$count])) {
- $items[$count]['raw'] = $render_array['#items'][$count];
+ if ((!isset($items[$delta]['rendered']['#access']) || $items[$delta]['rendered']['#access']) && !empty($build['#items'][$delta])) {
+ $items[$delta]['raw'] = $build['#items'][$delta];
}
}
return $items;
@@ -765,15 +841,15 @@ public function getItems(ResultRow $values) {
* The result row object containing the values.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be processed.
+ * @param bool $clone
+ * (optional) Whether a clone of the specified entity should processed.
+ * Defaults to TRUE.
*
- * @return
- * TRUE if the processing completed successfully, otherwise FALSE.
+ * @return bool|\Drupal\Core\Entity\FieldableEntityInterface
+ * Returns the processed entity if successful, else returns FALSE.
*/
- function process_entity(ResultRow $values, EntityInterface $entity) {
- $processed_entity = clone $entity;
-
- $langcode = $this->getFieldLangcode($processed_entity, $values);
- $processed_entity = $processed_entity->getTranslation($langcode);
+ public function processEntity(ResultRow $values, EntityInterface $entity, $clone = TRUE) {
+ $processed_entity = $clone ? clone $entity : $entity;
// If we are grouping, copy our group fields into the cloned entity.
// It's possible this will cause some weirdness, but there's only
@@ -811,57 +887,6 @@ function process_entity(ResultRow $values, EntityInterface $entity) {
return FALSE;
}
- // We are supposed to show only certain deltas.
- if ($this->limit_values && !empty($processed_entity->{$this->definition['field_name']})) {
- $all_values = !empty($processed_entity->{$this->definition['field_name']}) ? $processed_entity->{$this->definition['field_name']}->getValue() : array();
- if ($this->options['delta_reversed']) {
- $all_values = array_reverse($all_values);
- }
-
- // Offset is calculated differently when row grouping for a field is
- // not enabled. Since there are multiple rows, the delta needs to be
- // taken into account, so that different values are shown per row.
- if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($values->{$this->aliases['delta']})) {
- $delta_limit = 1;
- $offset = $values->{$this->aliases['delta']};
- }
- // Single fields don't have a delta available so choose 0.
- elseif (!$this->options['group_rows'] && !$this->multiple) {
- $delta_limit = 1;
- $offset = 0;
- }
- else {
- $delta_limit = $this->options['delta_limit'];
- $offset = intval($this->options['delta_offset']);
-
- // We should only get here in this case if there's an offset, and
- // in that case we're limiting to all values after the offset.
- if ($delta_limit === 0) {
- $delta_limit = count($all_values) - $offset;
- }
- }
-
- // Determine if only the first and last values should be shown
- $delta_first_last = $this->options['delta_first_last'];
-
- $new_values = array();
- for ($i = 0; $i < $delta_limit; $i++) {
- $new_delta = $offset + $i;
-
- if (isset($all_values[$new_delta])) {
- // If first-last option was selected, only use the first and last values
- if (!$delta_first_last
- // Use the first value.
- || $new_delta == $offset
- // Use the last value.
- || $new_delta == ($delta_limit + $offset - 1)) {
- $new_values[] = $all_values[$new_delta];
- }
- }
- }
- $processed_entity->{$this->definition['field_name']} = $new_values;
- }
-
return $processed_entity;
}
@@ -900,39 +925,6 @@ protected function addSelfTokens(&$tokens, $item) {
}
/**
- * Return the code of the language the field should be displayed in.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity object the field value being processed is attached to.
- * @param \Drupal\views\ResultRow $row
- * The result row the field value being processed belongs to.
- *
- * @return string
- * The field language code.
- */
- protected function getFieldLangcode(EntityInterface $entity, ResultRow $row) {
- if ($this->getFieldDefinition()->isTranslatable()) {
- // Even if the current field is not attached to the main entity, we use it
- // to determine the field language, as we assume the same language should
- // be used for all values belonging to a single row, when possible. Below
- // we apply language fallback to ensure a valid value is always picked.
- $langcode = $this->getEntityTranslationRenderer()->getLangcode($row);
-
- // Give the Entity Field API a chance to fallback to a different language
- // (or LanguageInterface::LANGCODE_NOT_SPECIFIED), in case the field has
- // no data for the selected language. FieldItemListInterface::view() does
- // this as well, but since the returned language code is used before
- // calling it, the fallback needs to happen explicitly.
- $langcode = $this->entityManager->getTranslationFromContext($entity, $langcode)->language()->getId();
-
- return $langcode;
- }
- else {
- return LanguageInterface::LANGCODE_NOT_SPECIFIED;
- }
- }
-
- /**
* {@inheritdoc}
*/
public function calculateDependencies() {
@@ -961,9 +953,7 @@ public function isCacheable() {
* {@inheritdoc}
*/
public function getCacheContexts() {
- $contexts = $this->getEntityTranslationRenderer()->getCacheContexts();
-
- return $contexts;
+ return $this->getEntityFieldRenderer()->getCacheContexts();
}
/**
@@ -980,11 +970,19 @@ protected function getTableMapping() {
* {@inheritdoc}
*/
public function getValue(ResultRow $values, $field = NULL) {
- if ($field === NULL) {
- return $this->getEntity($values)->{$this->definition['field_name']}->value;
+ /** @var \Drupal\Core\Field\FieldItemListInterface $field_item_list */
+ $field_item_list = $this->getEntity($values)->{$this->definition['field_name']};
+ $field_item_definition = $field_item_list->getFieldDefinition();
+
+ if ($field_item_definition->getFieldStorageDefinition()->getCardinality() == 1) {
+ return $field ? $field_item_list->$field : $field_item_list->value;
}
- return $this->getEntity($values)->{$this->definition['field_name']}->$field;
+ $values = [];
+ foreach ($field_item_list as $field_item) {
+ $values[] = $field ? $field_item->$field : $field_item->value;
+ }
+ return $values;
}
}
diff --git a/core/modules/views/src/Tests/Handler/FieldFieldTest.php b/core/modules/views/src/Tests/Handler/FieldFieldTest.php
index 4982cdd..0496ef4 100644
--- a/core/modules/views/src/Tests/Handler/FieldFieldTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldFieldTest.php
@@ -7,10 +7,12 @@
namespace Drupal\views\Tests\Handler;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\user\Entity\User;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Views;
@@ -31,7 +33,7 @@ class FieldFieldTest extends ViewUnitTestBase {
/**
* {@inheritdoc}
*/
- public static $testViews = ['test_field_field_test', 'test_field_field_revision_test'];
+ public static $testViews = ['test_field_field_test', 'test_field_alias_test', 'test_field_field_complex_test', 'test_field_field_revision_test', 'test_field_field_revision_complex_test'];
/**
* The stored test entities.
@@ -48,14 +50,45 @@ class FieldFieldTest extends ViewUnitTestBase {
protected $entityRevision;
/**
+ * Stores a couple of test users.
+ *
+ * @var \Drupal\user\UserInterface[]
+ */
+ protected $testUsers;
+
+ /**
+ * The admin user.
+ *
+ * @var \Drupal\user\UserInterface
+ */
+ protected $adminUser;
+
+ /**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
+ $this->installEntitySchema('user');
$this->installEntitySchema('entity_test_rev');
+ // Bypass any field access.
+ $this->adminUser = User::create([
+ 'admin_role' => true,
+ ]);
+ $this->adminUser->save();
+ $this->container->get('current_user')->setAccount($this->adminUser);
+
+ $this->testUsers = [];
+ for ($i = 0; $i < 5; $i++) {
+ $this->testUsers[$i] = User::create([
+ 'name' => 'test ' . $i,
+ 'timezone' => User::getAllowedTimezones()[$i],
+ ]);
+ $this->testUsers[$i]->save();
+ }
+
// Setup a field storage and field, but also change the views data for the
// entity_test entity type.
$field_storage = FieldStorageConfig::create([
@@ -72,11 +105,30 @@ protected function setUp() {
]);
$field->save();
+ $field_storage_multiple = FieldStorageConfig::create([
+ 'field_name' => 'field_test_multiple',
+ 'type' => 'integer',
+ 'entity_type' => 'entity_test',
+ 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+ ]);
+ $field_storage_multiple->save();
+
+ $field_multiple = FieldConfig::create([
+ 'field_name' => 'field_test_multiple',
+ 'entity_type' => 'entity_test',
+ 'bundle' => 'entity_test',
+ ]);
+ $field_multiple->save();
+
$random_number = (string) 30856;
+ $random_number_multiple = (string) 1370359990;
for ($i = 0; $i < 5; $i++) {
$this->entities[$i] = $entity = EntityTest::create([
'bundle' => 'entity_test',
+ 'name' => 'test ' . $i,
'field_test' => $random_number[$i],
+ 'field_test_multiple' => [$random_number_multiple[$i * 2], $random_number_multiple[$i * 2 + 1]],
+ 'user_id' => $this->testUsers[$i]->id(),
]);
$entity->save();
}
@@ -97,30 +149,58 @@ protected function setUp() {
]);
$field->save();
+ $field_storage_multiple = FieldStorageConfig::create([
+ 'field_name' => 'field_test_multiple',
+ 'type' => 'integer',
+ 'entity_type' => 'entity_test_rev',
+ 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+ ]);
+ $field_storage_multiple->save();
+
+ $field_multiple = FieldConfig::create([
+ 'field_name' => 'field_test_multiple',
+ 'entity_type' => 'entity_test_rev',
+ 'bundle' => 'entity_test_rev',
+ ]);
+ $field_multiple->save();
+
$this->entityRevision = [];
$this->entityRevision[0] = $entity = EntityTestRev::create([
'name' => 'base value',
'field_test' => 1,
+ 'field_test_multiple' => [1, 3, 7],
+ 'user_id' => $this->testUsers[0]->id(),
]);
$entity->save();
+ $original_entity = clone $entity;
- $entity = clone $entity;
+ $entity = clone $original_entity;
$entity->setNewRevision(TRUE);
$entity->name->value = 'revision value1';
$entity->field_test->value = 2;
+ $entity->field_test_multiple[0]->value = 0;
+ $entity->field_test_multiple[1]->value = 3;
+ $entity->field_test_multiple[2]->value = 5;
+ $entity->user_id->target_id = $this->testUsers[1]->id();
$entity->save();
$this->entityRevision[1] = $entity;
- $entity = clone $entity;
+ $entity = clone $original_entity;
$entity->setNewRevision(TRUE);
$entity->name->value = 'revision value2';
$entity->field_test->value = 3;
+ $entity->field_test_multiple[0]->value = 9;
+ $entity->field_test_multiple[1]->value = 9;
+ $entity->field_test_multiple[2]->value = 9;
+ $entity->user_id->target_id = $this->testUsers[2]->id();
$entity->save();
$this->entityRevision[2] = $entity;
- $this->entityRevision[0] = $entity = EntityTestRev::create([
+ $this->entityRevision[3] = $entity = EntityTestRev::create([
'name' => 'next entity value',
'field_test' => 4,
+ 'field_test_multiple' => [2, 9, 9],
+ 'user_id' => $this->testUsers[3]->id(),
]);
$entity->save();
@@ -171,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'));
@@ -186,6 +266,114 @@ public function testSimpleRender() {
}
/**
+ * Tests the result of a view with complex field configuration.
+ *
+ * A complex field configuration contains multiple times the same field, with
+ * different delta limit / offset.
+ */
+ public function testFieldAlias() {
+ $executable = Views::getView('test_field_alias_test');
+ $executable->execute();
+
+ $this->assertTrue($executable->field['id'] instanceof Field);
+ $this->assertTrue($executable->field['name'] instanceof Field);
+ $this->assertTrue($executable->field['name_alias'] instanceof Field);
+
+ $this->assertIdenticalResultset($executable,
+ [
+ ['id' => 1, 'name' => 'test 0', 'name_alias' => 'test 0'],
+ ['id' => 2, 'name' => 'test 1', 'name_alias' => 'test 1'],
+ ['id' => 3, 'name' => 'test 2', 'name_alias' => 'test 2'],
+ ['id' => 4, 'name' => 'test 3', 'name_alias' => 'test 3'],
+ ['id' => 5, 'name' => 'test 4', 'name_alias' => 'test 4'],
+ ],
+ ['id' => 'id', 'name' => 'name', 'name_alias' => 'name_alias']
+ );
+ }
+
+ /**
+ * Tests the result of a view with complex field configuration.
+ *
+ * A complex field configuration contains multiple times the same field, with
+ * different delta limit / offset.
+ */
+ public function testFieldAliasRender() {
+ $executable = Views::getView('test_field_alias_test');
+ $executable->render();
+
+ for ($i = 0; $i < 5; $i++) {
+ $this->assertEqual($i + 1, $executable->getStyle()->getField($i, 'id'));
+ $this->assertEqual('test ' . $i, $executable->getStyle()->getField($i, 'name'));
+ $this->assertEqual('test ' . $i . '', $executable->getStyle()->getField($i, 'name_alias'));
+ }
+ }
+
+ /**
+ * Tests the result of a view with complex field configuration.
+ *
+ * A complex field configuration contains multiple times the same field, with
+ * different delta limit / offset.
+ */
+ public function testComplexExecute() {
+ $executable = Views::getView('test_field_field_complex_test');
+ $executable->execute();
+
+ $timezones = [];
+ foreach ($this->testUsers as $user) {
+ $timezones[] = $user->getTimeZone();
+ }
+
+ $this->assertTrue($executable->field['field_test_multiple'] instanceof Field);
+ $this->assertTrue($executable->field['field_test_multiple_1'] instanceof Field);
+ $this->assertTrue($executable->field['field_test_multiple_2'] instanceof Field);
+ $this->assertTrue($executable->field['timezone'] instanceof Field);
+
+ $this->assertIdenticalResultset($executable,
+ [
+ ['timezone' => $timezones[0], 'field_test_multiple' => [1, 3], 'field_test_multiple_1' => [1, 3], 'field_test_multiple_2' => [1, 3]],
+ ['timezone' => $timezones[1], 'field_test_multiple' => [7, 0], 'field_test_multiple_1' => [7, 0], 'field_test_multiple_2' => [7, 0]],
+ ['timezone' => $timezones[2], 'field_test_multiple' => [3, 5], 'field_test_multiple_1' => [3, 5], 'field_test_multiple_2' => [3, 5]],
+ ['timezone' => $timezones[3], 'field_test_multiple' => [9, 9], 'field_test_multiple_1' => [9, 9], 'field_test_multiple_2' => [9, 9]],
+ ['timezone' => $timezones[4], 'field_test_multiple' => [9, 0], 'field_test_multiple_1' => [9, 0], 'field_test_multiple_2' => [9, 0]],
+ ],
+ ['timezone' => 'timezone', 'field_test_multiple' => 'field_test_multiple', 'field_test_multiple_1' => 'field_test_multiple_1', 'field_test_multiple_2' => 'field_test_multiple_2']
+ );
+ }
+
+ /**
+ * Tests the output of a view with complex field configuration.
+ */
+ public function testComplexRender() {
+ $executable = Views::getView('test_field_field_complex_test');
+ $executable->render();
+
+ $this->assertEqual($this->testUsers[0]->getTimeZone(), $executable->getStyle()->getField(0, 'timezone'));
+ $this->assertEqual("1, 3", $executable->getStyle()->getField(0, 'field_test_multiple'));
+ $this->assertEqual("1", $executable->getStyle()->getField(0, 'field_test_multiple_1'));
+ $this->assertEqual("3", $executable->getStyle()->getField(0, 'field_test_multiple_2'));
+
+ $this->assertEqual($this->testUsers[1]->getTimeZone(), $executable->getStyle()->getField(1, 'timezone'));
+ $this->assertEqual("7, 0", $executable->getStyle()->getField(1, 'field_test_multiple'));
+ $this->assertEqual("7", $executable->getStyle()->getField(1, 'field_test_multiple_1'));
+ $this->assertEqual("0", $executable->getStyle()->getField(1, 'field_test_multiple_2'));
+
+ $this->assertEqual($this->testUsers[2]->getTimeZone(), $executable->getStyle()->getField(2, 'timezone'));
+ $this->assertEqual("3, 5", $executable->getStyle()->getField(2, 'field_test_multiple'));
+ $this->assertEqual("3", $executable->getStyle()->getField(2, 'field_test_multiple_1'));
+ $this->assertEqual("5", $executable->getStyle()->getField(2, 'field_test_multiple_2'));
+
+ $this->assertEqual($this->testUsers[3]->getTimeZone(), $executable->getStyle()->getField(3, 'timezone'));
+ $this->assertEqual("9, 9", $executable->getStyle()->getField(3, 'field_test_multiple'));
+ $this->assertEqual("9", $executable->getStyle()->getField(3, 'field_test_multiple_1'));
+ $this->assertEqual("9", $executable->getStyle()->getField(3, 'field_test_multiple_2'));
+
+ $this->assertEqual($this->testUsers[4]->getTimeZone(), $executable->getStyle()->getField(4, 'timezone'));
+ $this->assertEqual("9, 0", $executable->getStyle()->getField(4, 'field_test_multiple'));
+ $this->assertEqual("9", $executable->getStyle()->getField(4, 'field_test_multiple_1'));
+ $this->assertEqual("0", $executable->getStyle()->getField(4, 'field_test_multiple_2'));
+ }
+
+ /**
* Tests the revision result.
*/
public function testRevisionExecute() {
@@ -207,11 +395,11 @@ public function testRevisionExecute() {
}
/**
- * Tests the output of a revision view with base fields and configurable fields.
+ * Tests the output of a revision view with base and configurable fields.
*/
public function testRevisionRender() {
$executable = Views::getView('test_field_field_revision_test');
- $executable->execute();
+ $executable->render();
$this->assertEqual(1, $executable->getStyle()->getField(0, 'id'));
$this->assertEqual(1, $executable->getStyle()->getField(0, 'revision_id'));
@@ -232,7 +420,72 @@ public function testRevisionRender() {
$this->assertEqual(4, $executable->getStyle()->getField(3, 'revision_id'));
$this->assertEqual(4, $executable->getStyle()->getField(3, 'field_test'));
$this->assertEqual('next entity value', $executable->getStyle()->getField(3, 'name'));
+ }
+ /**
+ * Tests the revision result.
+ */
+ public function testRevisionComplexExecute() {
+ $executable = Views::getView('test_field_field_revision_complex_test');
+ $executable->execute();
+
+ $timezones = [];
+ foreach ($this->testUsers as $user) {
+ $timezones[] = $user->getTimeZone();
+ }
+
+ $this->assertTrue($executable->field['id'] instanceof Field);
+ $this->assertTrue($executable->field['revision_id'] instanceof Field);
+ $this->assertTrue($executable->field['timezone'] instanceof Field);
+ $this->assertTrue($executable->field['field_test_multiple'] instanceof Field);
+ $this->assertTrue($executable->field['field_test_multiple_1'] instanceof Field);
+ $this->assertTrue($executable->field['field_test_multiple_2'] instanceof Field);
+
+ $this->assertIdenticalResultset($executable,
+ [
+ ['id' => 1, 'field_test' => 1, 'revision_id' => 1, 'uid' => $this->testUsers[0]->id(), 'timezone' => $timezones[0], 'field_test_multiple' => [1, 3, 7], 'field_test_multiple_1' => [1, 3, 7], 'field_test_multiple_2' => [1, 3, 7]],
+ ['id' => 1, 'field_test' => 2, 'revision_id' => 2, 'uid' => $this->testUsers[1]->id(), 'timezone' => $timezones[1], 'field_test_multiple' => [0, 3, 5], 'field_test_multiple_1' => [0, 3, 5], 'field_test_multiple_2' => [0, 3, 5]],
+ ['id' => 1, 'field_test' => 3, 'revision_id' => 3, 'uid' => $this->testUsers[2]->id(), 'timezone' => $timezones[2], 'field_test_multiple' => [9, 9, 9], 'field_test_multiple_1' => [9, 9, 9], 'field_test_multiple_2' => [9, 9, 9]],
+ ['id' => 2, 'field_test' => 4, 'revision_id' => 4, 'uid' => $this->testUsers[3]->id(), 'timezone' => $timezones[3], 'field_test_multiple' => [2, 9, 9], 'field_test_multiple_1' => [2, 9, 9], 'field_test_multiple_2' => [2, 9, 9]],
+ ],
+ ['entity_test_rev_revision_id' => 'id', 'revision_id' => 'revision_id', 'users_field_data_entity_test_rev_revision_uid' => 'uid', 'timezone' => 'timezone', 'field_test_multiple' => 'field_test_multiple', 'field_test_multiple_1' => 'field_test_multiple_1', 'field_test_multiple_2' => 'field_test_multiple_2']
+ );
+ }
+
+ /**
+ * Tests the output of a revision view with base fields and configurable fields.
+ */
+ public function testRevisionComplexRender() {
+ $executable = Views::getView('test_field_field_revision_complex_test');
+ $executable->render();
+
+ $this->assertEqual(1, $executable->getStyle()->getField(0, 'id'));
+ $this->assertEqual(1, $executable->getStyle()->getField(0, 'revision_id'));
+ $this->assertEqual($this->testUsers[0]->getTimeZone(), $executable->getStyle()->getField(0, 'timezone'));
+ $this->assertEqual('1, 3, 7', $executable->getStyle()->getField(0, 'field_test_multiple'));
+ $this->assertEqual('1', $executable->getStyle()->getField(0, 'field_test_multiple_1'));
+ $this->assertEqual('3, 7', $executable->getStyle()->getField(0, 'field_test_multiple_2'));
+
+ $this->assertEqual(1, $executable->getStyle()->getField(1, 'id'));
+ $this->assertEqual(2, $executable->getStyle()->getField(1, 'revision_id'));
+ $this->assertEqual($this->testUsers[1]->getTimeZone(), $executable->getStyle()->getField(1, 'timezone'));
+ $this->assertEqual('0, 3, 5', $executable->getStyle()->getField(1, 'field_test_multiple'));
+ $this->assertEqual('0', $executable->getStyle()->getField(1, 'field_test_multiple_1'));
+ $this->assertEqual('3, 5', $executable->getStyle()->getField(1, 'field_test_multiple_2'));
+
+ $this->assertEqual(1, $executable->getStyle()->getField(2, 'id'));
+ $this->assertEqual(3, $executable->getStyle()->getField(2, 'revision_id'));
+ $this->assertEqual($this->testUsers[2]->getTimeZone(), $executable->getStyle()->getField(2, 'timezone'));
+ $this->assertEqual('9, 9, 9', $executable->getStyle()->getField(2, 'field_test_multiple'));
+ $this->assertEqual('9', $executable->getStyle()->getField(2, 'field_test_multiple_1'));
+ $this->assertEqual('9, 9', $executable->getStyle()->getField(2, 'field_test_multiple_2'));
+
+ $this->assertEqual(2, $executable->getStyle()->getField(3, 'id'));
+ $this->assertEqual(4, $executable->getStyle()->getField(3, 'revision_id'));
+ $this->assertEqual($this->testUsers[3]->getTimeZone(), $executable->getStyle()->getField(3, 'timezone'));
+ $this->assertEqual('2, 9, 9', $executable->getStyle()->getField(3, 'field_test_multiple'));
+ $this->assertEqual('2', $executable->getStyle()->getField(3, 'field_test_multiple_1'));
+ $this->assertEqual('9, 9', $executable->getStyle()->getField(3, 'field_test_multiple_2'));
}
}
diff --git a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php
index a5f18d2..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,8 +86,13 @@ 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;
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[1]), 'b');
+ $view->row_index = 2;
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[2]), 'c');
}
diff --git a/core/modules/views/src/Tests/ViewResultAssertionTrait.php b/core/modules/views/src/Tests/ViewResultAssertionTrait.php
index b8ad34f..f88c8c5 100644
--- a/core/modules/views/src/Tests/ViewResultAssertionTrait.php
+++ b/core/modules/views/src/Tests/ViewResultAssertionTrait.php
@@ -113,7 +113,18 @@ protected function assertIdenticalResultsetHelper($view, $expected_result, $colu
$row = array();
foreach ($column_map as $expected_column) {
// The comparison will be done on the string representation of the value.
- $row[$expected_column] = (string) (is_object($value) ? $value->$expected_column : $value[$expected_column]);
+ if (is_object($value)) {
+ $row[$expected_column] = (string) $value->$expected_column;
+ }
+ // This case is about fields with multiple values.
+ elseif (is_array($value[$expected_column])) {
+ foreach (array_keys($value[$expected_column]) as $delta) {
+ $row[$expected_column][$delta] = (string) $value[$expected_column][$delta];
+ }
+ }
+ else {
+ $row[$expected_column] = (string) $value[$expected_column];
+ }
}
$expected_result[$key] = $row;
}
@@ -140,4 +151,3 @@ protected function assertIdenticalResultsetHelper($view, $expected_result, $colu
}
}
-
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml
new file mode 100644
index 0000000..6e178cc
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml
@@ -0,0 +1,64 @@
+langcode: und
+status: true
+dependencies: { }
+id: test_field_alias_test
+module: views
+description: ''
+tag: ''
+base_table: entity_test
+base_field: id
+core: '8'
+display:
+ default:
+ display_options:
+ access:
+ type: none
+ cache:
+ type: none
+ fields:
+ id:
+ id: id
+ table: entity_test
+ field: id
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: id
+ name:
+ id: name
+ table: entity_test
+ field: name
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: name
+ type: string
+ settings:
+ link_to_entity: false
+ name_alias:
+ id: name_alias
+ table: entity_test
+ field: name_alias
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: name
+ type: string
+ settings:
+ link_to_entity: true
+ relationships:
+ user_id:
+ table: entity_test
+ field: user_id
+ id: user_id
+ plugin_id: standard
+ sorts:
+ id:
+ id: id
+ table: entity_test
+ field: id
+ plugin_id: standard
+ order: asc
+ style:
+ type: html_list
+ display_plugin: default
+ display_title: Master
+ id: default
+ position: 0
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml
new file mode 100644
index 0000000..58ee430
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml
@@ -0,0 +1,79 @@
+langcode: und
+status: true
+dependencies: { }
+id: test_field_field_complex_test
+module: views
+description: ''
+tag: ''
+base_table: entity_test
+base_field: id
+core: '8'
+display:
+ default:
+ display_options:
+ access:
+ type: none
+ cache:
+ type: none
+ fields:
+ id:
+ id: id
+ table: entity_test
+ field: id
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: id
+ field_test_multiple:
+ id: field_test_multiple
+ table: entity_test__field_test_multiple
+ field: field_test_multiple
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: field_test_multiple
+ delta_limit: 0
+ group_rows: true
+ field_test_multiple_1:
+ id: field_test_multiple_1
+ table: entity_test__field_test_multiple
+ field: field_test_multiple
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: field_test_multiple
+ delta_limit: 1
+ group_rows: true
+ field_test_multiple_2:
+ id: field_test_multiple_2
+ table: entity_test__field_test_multiple
+ field: field_test_multiple
+ plugin_id: field
+ entity_type: entity_test
+ entity_field: field_test_multiple
+ delta_limit: 0
+ delta_offset: 1
+ group_rows: true
+ timezone:
+ id: timezone
+ table: users_field_data
+ field: timezone
+ plugin_id: field
+ relationship: user_id
+ alter: {}
+ relationships:
+ user_id:
+ table: entity_test
+ field: user_id
+ id: user_id
+ plugin_id: standard
+ sorts:
+ id:
+ id: id
+ table: entity_test
+ field: id
+ plugin_id: standard
+ order: asc
+ style:
+ type: html_list
+ display_plugin: default
+ display_title: Master
+ id: default
+ position: 0
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
similarity index 50%
copy from core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
copy to core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
index 5a49a08..b0122a3 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
@@ -1,7 +1,7 @@
langcode: und
status: true
dependencies: { }
-id: test_field_field_revision_test
+id: test_field_field_revision_complex_test
module: views
description: ''
tag: ''
@@ -30,20 +30,47 @@ display:
plugin_id: field
entity_type: entity_test_rev
entity_field: revision_id
- field_test:
- id: field_test
- table: entity_test__field_test
- field: field_test
+ field_test_multiple:
+ id: field_test_multiple
+ table: entity_test_rev__field_test_multiple
+ field: field_test_multiple
plugin_id: field
entity_type: entity_test_rev
- entity_field: field_test
- name:
- id: name
+ entity_field: field_test_multiple
+ delta_limit: 0
+ group_rows: true
+ field_test_multiple_1:
+ id: field_test_multiple_1
+ table: entity_test_rev__field_test_multiple
+ field: field_test_multiple
+ plugin_id: field
+ entity_type: entity_test_rev
+ entity_field: field_test_multiple
+ delta_limit: 1
+ group_rows: true
+ field_test_multiple_2:
+ id: field_test_multiple_2
+ table: entity_test_rev__field_test_multiple
+ field: field_test_multiple
+ plugin_id: field
+ entity_type: entity_test_rev
+ entity_field: field_test_multiple
+ delta_limit: 0
+ delta_offset: 1
+ group_rows: true
+ timezone:
+ id: timezone
+ table: users_field_data
+ field: timezone
+ plugin_id: field
+ relationship: user_id
+ alter: {}
+ relationships:
+ user_id:
table: entity_test_rev_revision
- field: name
- plugin_id: field
- entity_type: entity_test_rev
- entity_field: name
+ field: user_id
+ id: user_id
+ plugin_id: standard
sorts:
revision_id:
id: revision_id
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
index 5a49a08..d85d4d1 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
@@ -32,7 +32,7 @@ display:
entity_field: revision_id
field_test:
id: field_test
- table: entity_test__field_test
+ table: entity_test_rev__field_test
field: field_test
plugin_id: field
entity_type: entity_test_rev
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
index 9a04103..0e45537 100644
--- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
@@ -492,12 +492,12 @@ public function providerTestRenderAsLinkWithPathAndTokens() {
/**
* Sets up a test field.
*
- * @return \Drupal\Tests\views\Unit\Plugin\field\TestField|\PHPUnit_Framework_MockObject_MockObject
+ * @return \Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTestField|\PHPUnit_Framework_MockObject_MockObject
* The test field.
*/
protected function setupTestField(array $options = []) {
- /** @var \Drupal\Tests\views\Unit\Plugin\field\TestField $field */
- $field = $this->getMock('Drupal\Tests\views\Unit\Plugin\field\TestField', ['l'], [$this->configuration, $this->pluginId, $this->pluginDefinition]);
+ /** @var \Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTestField $field */
+ $field = $this->getMock('Drupal\Tests\views\Unit\Plugin\field\FieldPluginBaseTestField', ['l'], [$this->configuration, $this->pluginId, $this->pluginDefinition]);
$field->init($this->executable, $this->display, $options);
$field->setLinkGenerator($this->linkGenerator);
@@ -506,7 +506,7 @@ protected function setupTestField(array $options = []) {
}
-class TestField extends FieldPluginBase {
+class FieldPluginBaseTestField extends FieldPluginBase {
public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
$this->linkGenerator = $link_generator;
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
index 9ca73d4..90bd2cc 100644
--- a/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
@@ -7,9 +7,11 @@
namespace Drupal\Tests\views\Unit\Plugin\field;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\Tests\views\Unit\Plugin\HandlerTestTrait;
use Drupal\views\Plugin\views\field\Field;
+use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
@@ -438,6 +440,7 @@ public function testQueryWithGroupByForBaseField() {
];
$handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
$handler->view = $this->executable;
+ $handler->view->field = [$handler];
$this->setupLanguageRenderer($handler, $definition);
@@ -499,6 +502,7 @@ public function testQueryWithGroupByForConfigField() {
];
$handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
$handler->view = $this->executable;
+ $handler->view->field = [$handler];
$this->setupLanguageRenderer($handler, $definition);
@@ -551,6 +555,85 @@ public function testQueryWithGroupByForConfigField() {
}
/**
+ * @covers ::prepareItemsByDelta
+ *
+ * @dataProvider providerTestPrepareItemsByDelta
+ */
+ public function testPrepareItemsByDelta(array $options, array $expected_values) {
+ $definition = [
+ 'entity_type' => 'test_entity',
+ 'field_name' => 'integer',
+ ];
+ $handler = new FieldTestField([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
+ $handler->view = $this->executable;
+ $handler->view->field = [$handler];
+
+ $this->setupLanguageRenderer($handler, $definition);
+
+ $field_storage = $this->getConfigFieldStorage();
+ $field_storage->expects($this->any())
+ ->method('getCardinality')
+ ->willReturn(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+
+ $this->entityManager->expects($this->any())
+ ->method('getFieldStorageDefinitions')
+ ->with('test_entity')
+ ->willReturn([
+ 'integer' => $field_storage,
+ ]);
+
+ $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface');
+ $table_mapping
+ ->expects($this->any())
+ ->method('getFieldColumnName')
+ ->with($field_storage, 'value')
+ ->willReturn('integer_value');
+ $entity_storage = $this->getMock('Drupal\Core\Entity\Sql\SqlEntityStorageInterface');
+ $entity_storage->expects($this->any())
+ ->method('getTableMapping')
+ ->willReturn($table_mapping);
+ $this->entityManager->expects($this->any())
+ ->method('getStorage')
+ ->with('test_entity')
+ ->willReturn($entity_storage);
+
+ $options = [
+ 'group_column' => 'value',
+ 'group_columns' => [],
+ 'table' => 'test_entity__integer',
+ ] + $options;
+ $handler->init($this->executable, $this->display, $options);
+
+ $this->executable->row_index = 0;
+ $this->executable->result = [0 => new ResultRow([])];
+
+ $items = [3, 1, 4, 1, 5, 9];
+ $this->assertEquals($expected_values, $handler->executePrepareItemsByDelta($items));
+ }
+
+ /**
+ * Provides test data for testPrepareItemsByDelta().
+ */
+ public function providerTestPrepareItemsByDelta() {
+ $data = [];
+
+ // Let's display all values.
+ $data[] = [[], [3, 1, 4, 1, 5, 9]];
+ // Test just reversed deltas.
+ $data[] = [['delta_reversed' => TRUE], [9, 5, 1, 4, 1, 3]];
+
+ // Test combinations of delta limit, offset and first_last.
+ $data[] = [['group_rows' => TRUE, 'delta_limit' => 3], [3, 1, 4]];
+ $data[] = [['group_rows' => TRUE, 'delta_limit' => 3, 'delta_offset' => 2], [4, 1, 5]];
+ $data[] = [['group_rows' => TRUE, 'delta_reversed' => TRUE, 'delta_limit' => 3, 'delta_offset' => 2], [1, 4, 1]];
+ $data[] = [['group_rows' => TRUE, 'delta_first_last' => TRUE], [3, 9]];
+ $data[] = [['group_rows' => TRUE, 'delta_limit' => 1, 'delta_first_last' => TRUE], [3]];
+ $data[] = [['group_rows' => TRUE, 'delta_offset' => 1, 'delta_first_last' => TRUE], [1, 9]];
+
+ return $data;
+ }
+
+ /**
* Returns a mocked base field storage object.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -640,3 +723,11 @@ protected function setupLanguageRenderer(Field $handler, $definition) {
}
}
+
+class FieldTestField extends Field {
+
+ public function executePrepareItemsByDelta(array $all_values) {
+ return $this->prepareItemsByDelta($all_values);
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index 5762e9e..397cb9d 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -1139,6 +1139,9 @@ public function testGetTranslationFromContext() {
->method('getTranslation')
->with('custom_langcode')
->will($this->returnValue($translated_entity));
+ $entity->expects($this->any())
+ ->method('getTranslationLanguages')
+ ->will($this->returnValue([new Language(['id' => 'en']), new Language(['id' => 'custom_langcode'])]));
$this->assertSame($entity, $this->entityManager->getTranslationFromContext($entity));
$this->assertSame($translated_entity, $this->entityManager->getTranslationFromContext($entity, 'custom_langcode'));