diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index fcba978b43ff1261d231dc1c09036fa694e9c725..888fe410e40d6a77d8b4e179dfd4e3e9ee199ffc 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -857,7 +857,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 4ba0efabf48a74bb142fa02b38c9eb131b2b15cb..196fb89ec46451768bdbbcba6b806402e41a1053 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 with the field values from the multiple entities being rendered. */ public function prepareView(array $entities_items); diff --git a/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php b/core/modules/field/src/Tests/Views/HandlerFieldFieldTest.php index 92353f0d37da79da8f390ca1bd12fa4fcd017ec2..415b831c796e83e65edd247ae5341af7a80b225e 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/system/src/Tests/Entity/EntityTranslationTest.php b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php index c8549e6b30546da59fe2db2ee2362dcfc268426c..7d513eb0e621f07010a8c8a0f1b91b82317cbaf3 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 0000000000000000000000000000000000000000..d74af47a05bef8c54f6774125eda8f4a131a3416 --- /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 8470efdd10975322f13dc5b3a3a2c863c6fe5120..697ae8aac4dfa63701e7798b9ecc0f49624f46a9 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 bab75d8c57a7f58f714c630db7c9eaaa43e14f29..dbd3a72904ce27a269a8dec763e34cd0cec70780 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 0000000000000000000000000000000000000000..8865a5f186486a09aa5e5a7430a1dfed74b09d15 --- /dev/null +++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php @@ -0,0 +1,286 @@ +relationship = $relationship; + $this->entityManager = $entity_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 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) { + // The method is called for each field in each result row. In order to + // leverage multiple-entity building of formatter output, we build the + // render arrays for all fields in all rows on the first call. + if (!isset($this->build)) { + $this->build = $this->buildFields($this->view->result); + } + + if (isset($field)) { + $field_id = $field->options['id']; + // Pick the render array 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 by the unset() above. We have to 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; + } + + /** + * Builds the render arrays for all fields of all result rows. + * + * The output is built using EntityViewDisplay objects to leverage + * multiple-entity building and ensure a common code path with regular entity + * view. + * - Each relationship is handled by a separate EntityFieldRenderer instance, + * since it operates on its own set of entities. This also ensures different + * entity types are handled separately, as they imply different + * relationships. + * - Within each relationship, the fields to render are arranged in unique + * sets containing each field at most once (an EntityViewDisplay can + * only process a field once with given display options, but a View can + * contain the same field several times with different display options). + * - For each set of fields, entities are processed by bundle, so that + * formatters can operate on the proper field definition for the bundle. + * + * @param \Drupal\views\ResultRow[] $values + * An array of all ResultRow objects returned from the query. + * + * @return array + * A renderable array for the fields handled by this renderer. + * + * @see \Drupal\Core\Entity\Entity\EntityViewDisplay + */ + protected function buildFields(array $values) { + $build = []; + + if ($values && ($field_ids = $this->getRenderableFieldIds())) { + $entity_type_id = $this->getEntityTypeId(); + + // Collect the entities for the relationship, fetch the right translation, + // and group by bundle. For each result row, the corresponding entity can + // be obtained from any of the fields handlers, so we arbitrarily use the + // first one. + $entities_by_bundles = []; + $field = $this->view->field[current($field_ids)]; + foreach ($values as $result_row) { + $entity = $field->getEntity($result_row); + $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row); + } + + // Determine unique sets of fields that can be processed by the same + // display. Fields that appear several times in the View open additional + // "overflow" displays. + $display_sets = []; + foreach ($field_ids as $field_id) { + $field = $this->view->field[$field_id]; + $index = 0; + while (isset($display_sets[$index][$field->definition['field_name']])) { + $index++; + } + $display_sets[$index][$field_id] = $field; + } + + // For each set of fields, build the output by bundle. + foreach ($display_sets as $display_fields) { + foreach ($entities_by_bundles as $bundle => $bundle_entities) { + // Create the display, and configure the field display options. + $display = EntityViewDisplay::create([ + 'targetEntityType' => $entity_type_id, + 'bundle' => $bundle, + 'status' => TRUE, + ]); + foreach ($display_fields as $field_id => $field) { + $display->setComponent($field->definition['field_name'], [ + 'type' => $field->options['type'], + 'settings' => $field->options['settings'], + ]); + } + // Let the display build the render array for the entities. + $display_build = $display->buildMultiple($bundle_entities); + // Collect the field render arrays and index them using our internal + // row indexes and field IDs. + foreach ($display_build as $row_index => $entity_build) { + foreach ($display_fields as $field_id => $field) { + $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : []; + } + } + } + } + } + + return $build; + } + + /** + * Returns a list of names of entity fields to be rendered. + * + * @return string[] + * An associative array of views fields. + */ + protected function getRenderableFieldIds() { + $field_ids = []; + foreach ($this->view->field as $field_id => $field) { + if ($field instanceof Field && $field->relationship == $this->relationship) { + $field_ids[] = $field_id; + } + } + return $field_ids; + } + + /** + * Returns the entity translation matching the configured row language. + * + * @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 specified row. + */ + public 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); + } + +} diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php index 1be73642f1e53783a7175cd1310858a73829841d..f874c75fd958e19bb2bbc6fadb6150030d4083cf 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 0000000000000000000000000000000000000000..4da6672c17891004d26e2a11bedb2d6351089e40 --- /dev/null +++ b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php @@ -0,0 +1,59 @@ +view->rowPlugin->entityManager->getViewBuilder($this->entityType->id()); + + /** @var \Drupal\views\ResultRow $row */ + foreach ($result as $row) { + // @todo Take relationships into account. + // See https://www.drupal.org/node/2457999. + $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 a0baec6b917d1d4354a763588bae1feab895767a..deb1806e3ac838e97668db6b9fc88d91385cc2ac 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 @@ abstract class RendererBase implements CacheablePluginInterface { * * @var array */ - protected $build = array(); + protected $build; /** * Constructs a renderer object. @@ -78,17 +77,6 @@ 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. * @@ -97,38 +85,26 @@ abstract public function getLangcode(ResultRow $row); * @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 cf0c29629d6eb3a58220080d09b1af8291f39544..c94243b43162c327e9e200ace53e6d6d200377d6 100644 --- a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php +++ b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php @@ -12,9 +12,9 @@ use Drupal\views\ResultRow; /** - * Renders entity translations in their active language. + * Renders entity translations in their row 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 5013b2164267e604e36755caf0a4168bbde052f3..419271945e0c8584c9b7a752681f5f373ed31ed5 100644 --- a/core/modules/views/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -17,13 +17,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; @@ -31,7 +30,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * A field that displays fieldapi fields. + * A field that displays entity field data. * * @ingroup views_field_handlers * @@ -40,8 +39,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. * @@ -199,13 +203,6 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o } } - /** - * {@inheritdoc} - */ - public function getEntityTypeId() { - return $this->getEntityType(); - } - /** * {@inheritdoc} */ @@ -213,19 +210,6 @@ protected function getEntityManager() { return $this->entityManager; } - /** - * {@inheritdoc} - */ - protected function getLanguageManager() { - return $this->languageManager; - } - /** - * {@inheritdoc} - */ - protected function getView() { - return $this->view; - } - /** * {@inheritdoc} */ @@ -272,8 +256,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); } /** @@ -686,6 +670,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,110 @@ public function renderItems($items) { } } + /** + * Adapts the $items according to the delta configuration. + * + * This selects displayed deltas, reorders items, and takes offsets into + * account. + * + * @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, 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 is an offset, and in + // that case we are 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)) { + // This can be invoked during field handler initialization in which case + // view fields are not set yet. + if (!empty($this->view->field)) { + foreach ($this->view->field as $field) { + // An entity field renderer can handle only a single relationship. + if ($field->relationship == $this->relationship && isset($field->entityFieldRenderer)) { + $this->entityFieldRenderer = $field->entityFieldRenderer; + break; + } + } + } + if (!isset($this->entityFieldRenderer)) { + $entity_type = $this->entityManager->getDefinition($this->getEntityType()); + $this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->relationship, $this->languageManager, $entity_type, $this->entityManager); + } + } + return $this->entityFieldRenderer; + } + /** * Gets an array of items for the field. * @@ -716,76 +805,77 @@ 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(); + if (!$this->displayHandler->useGroupBy()) { + $build_list = $this->getEntityFieldRenderer()->render($values, $this); } - $entity = $this->process_entity($values, $original_entity); - if (!$entity) { - return array(); + else { + // For grouped results we need to retrieve a massaged entity having + // grouped field values to ensure that "grouped by" values, especially + // those with multiple cardinality work properly. See + // \Drupal\views\Tests\QueryGroupByTest::testGroupByFieldWithCardinality. + $display = [ + 'type' => $this->options['type'], + 'settings' => $this->options['settings'], + 'label' => 'hidden', + ]; + $build_list = $this->createEntityForGroupBy($this->getEntity($values), $values) + ->{$this->definition['field_name']} + ->view($display); } - $display = array( - 'type' => $this->options['type'], - 'settings' => $this->options['settings'], - 'label' => 'hidden', - ); - $render_array = $entity->get($this->definition['field_name'])->view($display); + if (!$build_list) { + return []; + } if ($this->options['field_api_classes']) { - return array(array('rendered' => $this->renderer->render($render_array))); + return [['rendered' => $this->renderer->render($build_list)]]; } - $items = array(); - foreach (Element::children($render_array) as $delta) { - $items[$delta]['rendered'] = $render_array[$delta]; + // Render using the formatted data itself. + $items = []; + foreach (Element::children($build_list) as $delta) { + $items[$delta]['rendered'] = $build_list[$delta]; // Merge the cacheability metadata of the top-level render array into // each child because they will most likely be rendered individually. - if (isset($render_array['#cache'])) { - CacheableMetadata::createFromRenderArray($render_array) + if (isset($build_list['#cache'])) { + CacheableMetadata::createFromRenderArray($build_list) ->merge(CacheableMetadata::createFromRenderArray($items[$delta]['rendered'])) ->applyTo($items[$delta]['rendered']); } // Add the raw field items (for use in tokens). - $items[$delta]['raw'] = $render_array['#items'][$delta]; + $items[$delta]['raw'] = $build_list['#items'][$delta]; } return $items; } /** - * Process an entity before using it for rendering. - * - * Replaces values with aggregated values if aggregation is enabled. - * Takes delta settings into account (@todo remove in #1758616). + * Creates a fake entity with grouped field values. * - * @param \Drupal\views\ResultRow $values - * The result row object containing the values. * @param \Drupal\Core\Entity\EntityInterface $entity * The entity to be processed. + * @param \Drupal\views\ResultRow $row + * The result row object containing the values. * - * @return - * TRUE if the processing completed successfully, otherwise FALSE. + * @return bool|\Drupal\Core\Entity\FieldableEntityInterface + * Returns a new entity object containing the grouped field values. */ - function process_entity(ResultRow $values, EntityInterface $entity) { - $processed_entity = clone $entity; + protected function createEntityForGroupBy(EntityInterface $entity, ResultRow $row) { + // Retrieve the correct translation object. + $processed_entity = clone $this->getEntityFieldRenderer()->getEntityTranslation($entity, $row); - $langcode = $this->getFieldLangcode($processed_entity, $values); - $processed_entity = $processed_entity->getTranslation($langcode); - - // If we are grouping, copy our group fields into the cloned entity. - // It's possible this will cause some weirdness, but there's only - // so much we can hope to do. - if (!empty($this->group_fields)) { + // Copy our group fields into the cloned entity. It is possible this will + // cause some weirdness, but there is only so much we can hope to do. + if (!empty($this->group_fields) && isset($entity->{$this->definition['field_name']})) { // first, test to see if we have a base value. $base_value = array(); // Note: We would copy original values here, but it can cause problems. - // For example, text fields store cached filtered values as - // 'safe_value' which doesn't appear anywhere in the field definition - // so we can't affect it. Other side effects could happen similarly. + // For example, text fields store cached filtered values as 'safe_value' + // which does not appear anywhere in the field definition so we cannot + // affect it. Other side effects could happen similarly. $data = FALSE; foreach ($this->group_fields as $field_name => $column) { - if (property_exists($values, $this->aliases[$column])) { - $base_value[$field_name] = $values->{$this->aliases[$column]}; + if (property_exists($row, $this->aliases[$column])) { + $base_value[$field_name] = $row->{$this->aliases[$column]}; if (isset($base_value[$field_name])) { $data = TRUE; } @@ -803,62 +893,6 @@ function process_entity(ResultRow $values, EntityInterface $entity) { } } - // The field we are trying to display doesn't exist on this entity. - if (!isset($processed_entity->{$this->definition['field_name']})) { - 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; } @@ -896,39 +930,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} */ @@ -958,9 +959,7 @@ public function isCacheable() { * {@inheritdoc} */ public function getCacheContexts() { - $contexts = $this->getEntityTranslationRenderer()->getCacheContexts(); - - return $contexts; + return $this->getEntityFieldRenderer()->getCacheContexts(); } /** @@ -977,11 +976,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 4982cdd97a06ed2ed463c34f0a60ed38dda942ba..942379b9b4e760c6575aa03f2c9713101622e0f0 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. @@ -47,6 +49,20 @@ 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} */ @@ -54,8 +70,23 @@ 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(); + $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 +103,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 +147,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(); @@ -185,6 +263,114 @@ public function testSimpleRender() { $this->assertEqual(6, $executable->getStyle()->getField(4, 'field_test')); } + /** + * 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->execute(); + + 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->execute(); + + $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. */ @@ -207,7 +393,7 @@ 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'); @@ -232,7 +418,93 @@ 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 result set of a complex revision view. + */ + 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->execute(); + + $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')); + } + + /** + * Tests that a field not available for every bundle is rendered as empty. + */ + public function testMissingBundleFieldRender() { + // Create a new bundle not having the test field attached. + $bundle = $this->randomMachineName(); + entity_test_create_bundle($bundle); + + $entity = EntityTest::create([ + 'type' => $bundle, + 'name' => $this->randomString(), + 'user_id' => $this->testUsers[0]->id(), + ]); + $entity->save(); + + $executable = Views::getView('test_field_field_test'); + $executable->execute(); + $this->assertIdentical('', $executable->getStyle()->getField(1, 'field_test')); } } diff --git a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php index a5f18d225a80ee1b8d33d296a5c39f8025cdc2a9..2aa0e9c482917a32bf7ff12e0aa83b41cb15e2b3 100644 --- a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php +++ b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php @@ -85,8 +85,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/QueryGroupByTest.php b/core/modules/views/src/Tests/QueryGroupByTest.php index b580e0a8b50d73f1114170942b42aab5a3f95d6a..ad3e225038b9c6060f9c59f41cf9f8246537cd68 100644 --- a/core/modules/views/src/Tests/QueryGroupByTest.php +++ b/core/modules/views/src/Tests/QueryGroupByTest.php @@ -7,6 +7,10 @@ namespace Drupal\views\Tests; +use Drupal\entity_test\Entity\EntityTestMul; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\views\Views; /** @@ -21,14 +25,14 @@ class QueryGroupByTest extends ViewUnitTestBase { * * @var array */ - public static $testViews = array('test_group_by_in_filters', 'test_aggregate_count', 'test_group_by_count'); + public static $testViews = array('test_group_by_in_filters', 'test_aggregate_count', 'test_group_by_count', 'test_group_by_count_multicardinality'); /** * Modules to enable. * * @var array */ - public static $modules = array('entity_test', 'system', 'field', 'user'); + public static $modules = array('entity_test', 'system', 'field', 'user', 'language'); /** * The storage for the test entity type. @@ -45,8 +49,11 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installEntitySchema('entity_test'); + $this->installEntitySchema('entity_test_mul'); $this->storage = $this->container->get('entity.manager')->getStorage('entity_test'); + + ConfigurableLanguage::createFromLangcode('it')->save(); } @@ -205,4 +212,84 @@ public function testGroupByBaseField() { $this->assertTrue(strpos($view->build_info['query'], 'GROUP BY entity_test.id'), 'GROUP BY field includes the base table name when grouping on the base field.'); } + /** + * Tests grouping a field with cardinality > 1. + */ + public function testGroupByFieldWithCardinality() { + $field_storage = FieldStorageConfig::create([ + 'type' => 'integer', + 'field_name' => 'field_test', + 'cardinality' => 4, + 'entity_type' => 'entity_test_mul', + ]); + $field_storage->save(); + $field = FieldConfig::create([ + 'field_name' => 'field_test', + 'entity_type' => 'entity_test_mul', + 'bundle' => 'entity_test_mul', + ]); + $field->save(); + + $entities = []; + $entity = EntityTestMul::create([ + 'field_test' => [1, 1, 1], + ]); + $entity->save(); + $entities[] = $entity; + + $entity = EntityTestMul::create([ + 'field_test' => [2, 2, 2], + ]); + $entity->save(); + $entities[] = $entity; + + $entity = EntityTestMul::create([ + 'field_test' => [2, 2, 2], + ]); + $entity->save(); + $entities[] = $entity; + + $view = Views::getView('test_group_by_count_multicardinality'); + $this->executeView($view); + $this->assertEqual(2, count($view->result)); + + $this->assertEqual(3, $view->getStyle()->getField(0, 'id')); + $this->assertEqual('1', $view->getStyle()->getField(0, 'field_test')); + $this->assertEqual(6, $view->getStyle()->getField(1, 'id')); + $this->assertEqual('2', $view->getStyle()->getField(1, 'field_test')); + + $entities[2]->field_test[0]->value = 3; + $entities[2]->field_test[1]->value = 4; + $entities[2]->field_test[2]->value = 5; + $entities[2]->save(); + + $view = Views::getView('test_group_by_count_multicardinality'); + $this->executeView($view); + $this->assertEqual(5, count($view->result)); + + $this->assertEqual(3, $view->getStyle()->getField(0, 'id')); + $this->assertEqual('1', $view->getStyle()->getField(0, 'field_test')); + $this->assertEqual(3, $view->getStyle()->getField(1, 'id')); + $this->assertEqual('2', $view->getStyle()->getField(1, 'field_test')); + $this->assertEqual(1, $view->getStyle()->getField(2, 'id')); + $this->assertEqual('3', $view->getStyle()->getField(2, 'field_test')); + $this->assertEqual(1, $view->getStyle()->getField(3, 'id')); + $this->assertEqual('4', $view->getStyle()->getField(3, 'field_test')); + $this->assertEqual(1, $view->getStyle()->getField(4, 'id')); + $this->assertEqual('5', $view->getStyle()->getField(4, 'field_test')); + + // Check that translated values are correctly retrieved and are not grouped + // into the original entity. + $translation = $entity->addTranslation('it'); + $translation->field_test = [6, 6, 6]; + $translation->save(); + + $view = Views::getView('test_group_by_count_multicardinality'); + $this->executeView($view); + + $this->assertEqual(6, count($view->result)); + $this->assertEqual(3, $view->getStyle()->getField(5, 'id')); + $this->assertEqual('6', $view->getStyle()->getField(5, 'field_test')); + } + } diff --git a/core/modules/views/src/Tests/ViewResultAssertionTrait.php b/core/modules/views/src/Tests/ViewResultAssertionTrait.php index b8ad34fd263683174634609e5a9aa36f1d99567e..f88c8c5f0e43643f37ce731832d3748afd575b27 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 0000000000000000000000000000000000000000..6e178cc9f62faf0bf1f07b53163b9ae17b9c7346 --- /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 0000000000000000000000000000000000000000..58ee430e369e89ed5c97ac2c76f01e423364e855 --- /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_complex_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml new file mode 100644 index 0000000000000000000000000000000000000000..b0122a3058e3ab2960074be219e681430ec8e4b8 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml @@ -0,0 +1,87 @@ +langcode: und +status: true +dependencies: { } +id: test_field_field_revision_complex_test +module: views +description: '' +tag: '' +base_table: entity_test_rev_revision +base_field: id +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: none + fields: + id: + id: id + table: entity_test_rev_revision + field: id + plugin_id: field + entity_type: entity_test_rev + entity_field: id + revision_id: + id: revision_id + table: entity_test_rev_revision + field: revision_id + plugin_id: field + entity_type: entity_test_rev + entity_field: revision_id + 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_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: user_id + id: user_id + plugin_id: standard + sorts: + revision_id: + id: revision_id + table: entity_test_rev_revision + field: revision_id + entity_type: entity_test_rev + entity_field: revision_id + 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 5a49a085c951de79ec218fc0d1860f6ca71f4216..d85d4d165c831e8245efd6bead63c36fa543083e 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/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml new file mode 100644 index 0000000000000000000000000000000000000000..9020ee1266f70c7fe939fa2ce5d552a142ccd48a --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml @@ -0,0 +1,80 @@ +langcode: en +status: true +dependencies: { } +id: test_group_by_count_multicardinality +label: '' +module: views +description: '' +tag: '' +base_table: entity_test_mul_property_data +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: none + exposed_form: + type: basic + fields: + id: + alter: + alter_text: false + ellipsis: true + html: false + make_link: false + strip_tags: false + trim: false + word_boundary: true + empty_zero: false + field: id + group_type: count + hide_empty: false + id: id + table: entity_test_mul_property_data + plugin_id: field + entity_type: entity_test_mul + entity_field: id + plugin_id: field + field_test: + alter: + alter_text: false + ellipsis: true + html: false + make_link: false + strip_tags: false + trim: false + word_boundary: true + group_type: group + group_column: value + empty_zero: false + field: field_test + hide_empty: false + id: field_test + table: entity_test_mul__field_test + entity_type: entity_test_mul + entity_field: field_test + plugin_id: field + sorts: + field_test_value: + table: entity_test__field_test + field: field_test + id: field_ttest_value + entity_type: entity_test_mul + entity_field: field_test + group_type: group + order: ASC + plugin_id: standard + group_by: true + pager: + type: some + style: + type: default + row: + type: fields + display_plugin: default + display_title: Master + id: default + position: 0 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 9a04103e9f562c78b3378920d093f758e57bfbdf..0e4553705b4996bbcd2b0877f3090d2c74ebc42a 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 9ca73d4642fd50132035cfe890588bed9cafa158..90bd2ccb20508446e20bd1b5c82a81de950f5dc1 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); @@ -550,6 +554,85 @@ public function testQueryWithGroupByForConfigField() { $handler->query(TRUE); } + /** + * @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. * @@ -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 5762e9eb9c31b94983794ce2f85a0c1320d4e8dc..397cb9dc6b69873995f3625f5315ce23137e301d 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'));