Issue #1867518 by plach, dawehner, yched, iMiksu, epari.siva, marcvangend, Fabianx, Wim Leers, effulgentsia: Leverage entityDisplay to provide fast rendering for fields
parent
74802813c8
commit
13922dbdc1
|
@ -857,7 +857,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ interface FormatterInterface extends PluginSettingsInterface {
|
|||
* 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);
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
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 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
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 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
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 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
$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 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
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 @@ class HandlerFieldFieldTest extends FieldTestBase {
|
|||
foreach ($pure_items as $j => $item) {
|
||||
$items[] = $pure_items[$j]['value'];
|
||||
}
|
||||
$this->assertEqual($rendered_field, implode('<h2>test</h2>', $items), 'Make sure that the amount of items is limited.');
|
||||
$this->assertEqual($rendered_field, implode('<h2>test</h2>', $items), 'The custom separator is correctly escaped.');
|
||||
}
|
||||
$view->destroy();
|
||||
}
|
||||
|
|
|
@ -383,10 +383,20 @@ class EntityTranslationTest extends EntityLanguageTestBase {
|
|||
|
||||
// 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 @@ class EntityTranslationTest extends EntityLanguageTestBase {
|
|||
catch (\LogicException $e) {
|
||||
$this->pass($message);
|
||||
}
|
||||
|
||||
$this->assertEqual($translation->{$default_langcode_key}->value, $default);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*/
|
||||
function entity_test_views_data_alter(&$data) {
|
||||
$data['entity_test']['name_alias'] = $data['entity_test']['name'];
|
||||
$data['entity_test']['name_alias']['field']['real field'] = 'name';
|
||||
}
|
|
@ -33,6 +33,7 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
$view->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');
|
||||
|
|
|
@ -15,7 +15,7 @@ use Drupal\views\ViewExecutable;
|
|||
/**
|
||||
* Renders entities in a configured language.
|
||||
*/
|
||||
class ConfigurableLanguageRenderer extends RendererBase {
|
||||
class ConfigurableLanguageRenderer extends EntityTranslationRendererBase {
|
||||
|
||||
/**
|
||||
* A specific language code for rendering if available.
|
||||
|
|
|
@ -12,7 +12,7 @@ use Drupal\views\ResultRow;
|
|||
/**
|
||||
* Renders entities in their default language.
|
||||
*/
|
||||
class DefaultLanguageRenderer extends RendererBase {
|
||||
class DefaultLanguageRenderer extends EntityTranslationRendererBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\views\Entity\Render\EntityFieldRenderer.
|
||||
*/
|
||||
|
||||
namespace Drupal\views\Entity\Render;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\views\Plugin\views\field\Field;
|
||||
use Drupal\views\Plugin\views\query\QueryPluginBase;
|
||||
use Drupal\views\ResultRow;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Renders entity fields.
|
||||
*
|
||||
* This is used to build render arrays for all entity field values of a view
|
||||
* result set sharing the same relationship. An entity translation renderer is
|
||||
* used internally to handle entity language properly.
|
||||
*/
|
||||
class EntityFieldRenderer extends RendererBase {
|
||||
use EntityTranslationRenderTrait;
|
||||
|
||||
/**
|
||||
* The relationship being handled.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* A list of indexes of rows whose fields have already been rendered.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
protected $processedRows = [];
|
||||
|
||||
/**
|
||||
* Constructs an EntityFieldRenderer object.
|
||||
*
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The view whose fields are being rendered.
|
||||
* @param string $relationship
|
||||
* The relationship to be handled.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
|
||||
parent::__construct($view, $language_manager, $entity_type);
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -10,25 +10,25 @@ namespace Drupal\views\Entity\Render;
|
|||
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 @@ trait EntityTranslationRenderTrait {
|
|||
}
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\views\Entity\Render\EntityTranslationRendererBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\views\Entity\Render;
|
||||
|
||||
use Drupal\views\Plugin\views\query\QueryPluginBase;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* Defines a base class for entity translation renderers.
|
||||
*/
|
||||
abstract class EntityTranslationRendererBase extends RendererBase {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query(QueryPluginBase $query, $relationship = NULL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(array $result) {
|
||||
$view_builder = $this->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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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\ResultRow;
|
|||
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 @@ abstract class RendererBase implements CacheablePluginInterface {
|
|||
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 class RendererBase implements CacheablePluginInterface {
|
|||
* @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);
|
||||
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ use Drupal\views\Plugin\views\query\QueryPluginBase;
|
|||
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.
|
||||
|
|
|
@ -17,13 +17,12 @@ use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
|||
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 Drupal\views\ViewExecutable;
|
|||
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 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* @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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityTypeId() {
|
||||
return $this->getEntityType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -213,19 +210,6 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getLanguageManager() {
|
||||
return $this->languageManager;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getView() {
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -272,8 +256,8 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
$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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
*/
|
||||
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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
* 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.
|
||||
* Creates a fake entity with grouped field values.
|
||||
*
|
||||
* Replaces values with aggregated values if aggregation is enabled.
|
||||
* Takes delta settings into account (@todo remove in #1758616).
|
||||
*
|
||||
* @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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
}
|
||||
}
|
||||
|
||||
// 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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
$contexts = $this->getEntityTranslationRenderer()->getCacheContexts();
|
||||
|
||||
return $contexts;
|
||||
return $this->getEntityFieldRenderer()->getCacheContexts();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -977,11 +976,19 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
|
|||
* {@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
]);
|
||||
$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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
]);
|
||||
$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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
$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('<a href="' . EntityTest::load($i + 1)->url() . '">test ' . $i . '</a>', $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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class FieldFieldTest extends ViewUnitTestBase {
|
|||
$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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -85,8 +85,13 @@ class FieldGroupRowsTest extends HandlerTestBase {
|
|||
|
||||
// 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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@ class QueryGroupByTest extends ViewUnitTestBase {
|
|||
|
||||
$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 @@ class QueryGroupByTest extends ViewUnitTestBase {
|
|||
$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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -113,7 +113,18 @@ trait ViewResultAssertionTrait {
|
|||
$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 @@ trait ViewResultAssertionTrait {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -492,12 +492,12 @@ class FieldPluginBaseTest extends UnitTestCase {
|
|||
/**
|
||||
* 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 @@ class FieldPluginBaseTest extends UnitTestCase {
|
|||
|
||||
}
|
||||
|
||||
class TestField extends FieldPluginBase {
|
||||
class FieldPluginBaseTestField extends FieldPluginBase {
|
||||
|
||||
public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
|
||||
$this->linkGenerator = $link_generator;
|
||||
|
|
|
@ -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 @@ class FieldTest extends UnitTestCase {
|
|||
];
|
||||
$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 @@ class FieldTest extends UnitTestCase {
|
|||
];
|
||||
$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 @@ class FieldTest extends UnitTestCase {
|
|||
$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 @@ class FieldTest extends UnitTestCase {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
class FieldTestField extends Field {
|
||||
|
||||
public function executePrepareItemsByDelta(array $all_values) {
|
||||
return $this->prepareItemsByDelta($all_values);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1139,6 +1139,9 @@ class EntityManagerTest extends UnitTestCase {
|
|||
->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'));
|
||||
|
|
Loading…
Reference in New Issue