diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index d315b27..acf3040 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -585,7 +585,7 @@ public function onChange($name) { case $this->defaultLangcodeKey: // @todo Use a standard method to make the default_langcode field // read-only. See https://www.drupal.org/node/2443991. - if (isset($this->values[$this->defaultLangcodeKey])) { + if (isset($this->values[$this->defaultLangcodeKey]) && $this->get($this->defaultLangcodeKey)->value != $this->isDefaultTranslation()) { $this->get($this->defaultLangcodeKey)->setValue($this->isDefaultTranslation(), FALSE); $message = String::format('The default translation flag cannot be changed (@langcode).', array('@langcode' => $this->activeLangcode)); throw new \LogicException($message); diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 071de3e..99551fc 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -316,8 +316,7 @@ public function buildComponents(array &$build, array $entities, array $displays, $entities_by_bundle[$entity->bundle()][$id] = $entity; } - // Invoke hook_entity_prepare_view(). - $this->moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode)); + $this->prepareView($entities, $displays, $view_mode); // Let the displays build their render arrays. foreach ($entities_by_bundle as $bundle => $bundle_entities) { @@ -329,6 +328,14 @@ public function buildComponents(array &$build, array $entities, array $displays, } /** + * {@inheritdoc} + */ + public function prepareView($entities, $displays, $view_mode) { + // Invoke hook_entity_prepare_view(). + $this->moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode)); + } + + /** * Specific per-entity building. * * @param array $build diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php index c3e167a..2d5f48c 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php @@ -36,6 +36,19 @@ public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL); /** + * Prepares the specified entities for rendering. + * + * @param \Drupal\Core\Entity\EntityInterface[] $entities + * The entities whose content is being built. + * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays + * The array of entity view displays holding the display options + * configured for the entity components, keyed by bundle name. + * @param $view_mode + * The view mode in which the entity is being viewed. + */ + public function prepareView($entities, $displays, $view_mode); + + /** * Returns the render array for the provided entity. * * @param \Drupal\Core\Entity\EntityInterface $entity diff --git a/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php b/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php index c017ded..92ea637 100644 --- a/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php +++ b/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php @@ -205,7 +205,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. @@ -227,7 +227,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(); @@ -248,7 +248,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(); @@ -266,7 +266,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(); @@ -286,7 +286,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(); @@ -305,7 +305,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/rdf/rdf.module b/core/modules/rdf/rdf.module index 50b6f36..b4bca6f 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -217,6 +217,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 aab0d28..c7eee60 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/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..82b9d07 --- /dev/null +++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php @@ -0,0 +1,355 @@ +entityManager = $entity_manager; + } + + /** + * {@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) { + $this->getEntityTranslationRenderer()->query($query); + } + + /** + * {@inheritdoc} + */ + public function preRender(array $values) { + $this->buildFields($values); + } + + /** + * 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) { + $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->field); + $entity_type_ids = array_combine( + array_keys($row_entities), + array_map(function (EntityInterface $entity) { return $entity->getEntityTypeId(); }, $row_entities) + ); + } + + // Process all entities for each entity type. + foreach ($entity_type_ids as $rel_field_name => $entity_type_id) { + $entity = isset($this->view->field[$rel_field_name]) ? $this->view->field[$rel_field_name]->getEntity($row) : $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->getRenderableFieldNames(); + $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, $this->getCustomViewMode($entity_type_id)); + } + + // 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. + */ + protected function getRenderableFieldNames() { + if (!isset($this->renderableFieldNames)) { + $this->renderableFieldNames = []; + foreach ($this->view->field as $view_field_name => $field) { + if ($field instanceof Field) { + $entity_type_id = $field->definition['entity_type']; + $this->renderableFieldNames[$entity_type_id][$view_field_name] = $field->definition['field_name']; + } + } + } + return $this->renderableFieldNames; + } + + /** + * 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); + } + + /** + * Returns the custom view mode to be used for field rendering. + * + * @param string $entity_type_id + * The type of the entities whose fields are being rendered. + * + * @return string + * A custom view mode name. + */ + protected function getCustomViewMode($entity_type_id) { + return 'views:' . $this->view->storage->id() . ':' . $this->view->current_display . ':' . $entity_type_id; + } + + /** + * 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'); + $view_mode = $this->getCustomViewMode($entity_type_id); + + // 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' => $view_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 + * 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) { + $build = $this->build; + + if (isset($field)) { + $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; + } + + $build = isset($build[$entity_type_id][$key][$field_name]) ? $build[$entity_type_id][$key][$field_name] : []; + } + } + + 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/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..bb9b8cc --- /dev/null +++ b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php @@ -0,0 +1,57 @@ +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 be8aea6..137fe1d 100644 --- a/core/modules/views/src/Entity/Render/RendererBase.php +++ b/core/modules/views/src/Entity/Render/RendererBase.php @@ -14,7 +14,7 @@ use Drupal\views\ViewExecutable; /** - * Defines a base class for entity row renderers. + * Defines a base class for entity renderers. */ abstract class RendererBase { @@ -63,54 +63,30 @@ public function __construct(ViewExecutable $view, LanguageManagerInterface $lang } /** - * 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 * The query to alter. */ - public function query(QueryPluginBase $query) { - } + abstract public function query(QueryPluginBase $query); /** - * 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)); - } - } + abstract public function preRender(array $result); /** - * 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 58c8ff0..093b249 100644 --- a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php +++ b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php @@ -13,7 +13,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/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php index 46d3f5f..d9e905a 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); + // Let the entity field renderer alter the query if needed. + $this->getEntityFieldRenderer()->query($this->query); } /** @@ -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,106 @@ public function renderItems($items) { } /** + * Adapts the $items according to the delta configuration. + * + * Therefore this method filters out limits the items, reorders the items + * as well as takes 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() { + if (!isset($this->entityFieldRenderer)) { + foreach ($this->view->field as $field) { + if (isset($field->entityFieldRenderer)) { + $this->entityFieldRenderer = $field->entityFieldRenderer; + break; + } + } + if (!isset($this->entityFieldRenderer)) { + $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); + } + } + return $this->entityFieldRenderer; + } + + /** * Gets an array of items for the field. * * @param \Drupal\views\ResultRow $values @@ -715,41 +800,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 +840,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 +886,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 +924,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() { @@ -983,11 +974,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..7d5fdd8 100644 --- a/core/modules/views/src/Tests/Handler/FieldFieldTest.php +++ b/core/modules/views/src/Tests/Handler/FieldFieldTest.php @@ -7,6 +7,7 @@ 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; @@ -31,7 +32,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_field_complex_test', 'test_field_field_revision_test']; /** * The stored test entities. @@ -72,11 +73,28 @@ 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', 'field_test' => $random_number[$i], + 'field_test_multiple' => [$random_number_multiple[$i * 2], $random_number_multiple[$i * 2 + 1]], ]); $entity->save(); } @@ -97,10 +115,25 @@ protected function setUp() { ]); $field->save(); + $field_storage_multiple = FieldStorageConfig::create([ + 'field_name' => 'field_test_multiple', + 'type' => 'integer', + 'entity_type' => 'entity_test_rev', + ]); + $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] ]); $entity->save(); @@ -108,6 +141,7 @@ protected function setUp() { $entity->setNewRevision(TRUE); $entity->name->value = 'revision value1'; $entity->field_test->value = 2; + $entity->field_test_multiple->value = 2; $entity->save(); $this->entityRevision[1] = $entity; @@ -186,6 +220,60 @@ 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 testComplexExecute() { + $executable = Views::getView('test_field_field_complex_test'); + $executable->execute(); + + $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, + [ + ['field_test_multiple' => ['1', '3'], 'field_test_multiple_1' => ['1', '3'], 'field_test_multiple_2' => ['1', '3']], + ['field_test_multiple' => ['7', '0'], 'field_test_multiple_1' => ['7', '0'], 'field_test_multiple_2' => ['7', '0']], + ['field_test_multiple' => ['3', '5'], 'field_test_multiple_1' => ['3', '5'], 'field_test_multiple_2' => ['3', '5']], + ['field_test_multiple' => ['9', '9'], 'field_test_multiple_1' => ['9', '9'], 'field_test_multiple_2' => ['9', '9']], + ['field_test_multiple' => ['9', '0'], 'field_test_multiple_1' => ['9', '0'], 'field_test_multiple_2' => ['9', '0']], + ], + ['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("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("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("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("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("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() { @@ -211,7 +299,7 @@ public function testRevisionExecute() { */ 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')); diff --git a/core/modules/views/src/Tests/ViewResultAssertionTrait.php b/core/modules/views/src/Tests/ViewResultAssertionTrait.php index 222bf25..96bdbbe 100644 --- a/core/modules/views/src/Tests/ViewResultAssertionTrait.php +++ b/core/modules/views/src/Tests/ViewResultAssertionTrait.php @@ -109,7 +109,15 @@ 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; + } + elseif (is_array($value[$expected_column])) { + $row[$expected_column] = $value[$expected_column]; + } + else { + $row[$expected_column] = (string) $value[$expected_column]; + } } $expected_result[$key] = $row; } 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..666b1ba --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml @@ -0,0 +1,66 @@ +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 + 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_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/FieldTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php index 9ca73d4..0341859 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,82 @@ 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 TestField([], '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)); + } + + 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 +720,11 @@ protected function setupLanguageRenderer(Field $handler, $definition) { } } + +class TestField extends Field { + + public function executePrepareItemsByDelta(array $all_values) { + return $this->prepareItemsByDelta($all_values); + } + +}