Commit 96e7aa9a authored by webchick's avatar webchick

Issue #2006606 by plach, vijaycs85, Gábor Hojtsy, dawehner, YesCT, pfrenssen:...

Issue #2006606 by plach, vijaycs85, Gábor Hojtsy, dawehner, YesCT, pfrenssen: Views rendering ignores entity language, eg: Identical nodes rendered in views when nodes have translations.
parent d6c9c575
......@@ -45,8 +45,7 @@ public function buildOptionsForm(&$form, &$form_state) {
* {@inheritdoc}
*/
public function render($row) {
$entity_id = $row->{$this->field_alias};
$build = $this->build[$entity_id];
$build = parent::render($row);
if (!$this->options['links']) {
unset($build['links']);
}
......
......@@ -246,6 +246,19 @@ function testTranslationRendering() {
$this->rebuildContainer();
$this->doTestTranslations('node', $values);
// Enable the translation language renderer.
$view = \Drupal::entityManager()->getStorageController('view')->load('frontpage');
$display = &$view->getDisplay('default');
$display['display_options']['row']['options']['rendering_language'] = 'translation_language_renderer';
$view->save();
// Test that the frontpage view displays all translated nodes correctly by
// checking that the title for each translation is present.
$this->drupalGet('node');
foreach ($this->langcodes as $langcode) {
$this->assertText($values[$langcode]['title'][0]['value']);
}
// Test that the node page displays the correct translations.
$this->doTestTranslations('node/' . $node->id(), $values);
}
......
......@@ -178,7 +178,7 @@ function user_attach_accounts(array $entities) {
}
$uids = array_unique($uids);
$accounts = user_load_multiple($uids);
$anonymous = drupal_anonymous_user();
$anonymous = entity_create('user', array('uid' => 0));
foreach ($entities as $id => $entity) {
if (isset($accounts[$entity->getOwnerId()])) {
$entities[$id]->setOwner($accounts[$entity->getOwnerId()]);
......
<?php
/**
* @file
* Contains \Drupal\views\Entity\Render\CurrentLanguageRenderer.
*/
namespace Drupal\views\Entity\Render;
/**
* Renders entities in the current language.
*/
class CurrentLanguageRenderer extends RendererBase {
}
<?php
/**
* @file
* Contains \Drupal\views\Entity\Render\DefaultLanguageRenderer.
*/
namespace Drupal\views\Entity\Render;
use Drupal\views\ResultRow;
/**
* Renders entities in their default language.
*/
class DefaultLanguageRenderer extends RendererBase {
/**
* {@inheritdoc}
*/
public function preRender(array $result) {
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
$entities = array();
$langcodes = array();
/** @var \Drupal\views\ResultRow $row */
foreach ($result as $row) {
$entity = $row->_entity;
$entity->view = $this->view;
$langcodes[] = $langcode = $this->getLangcode($row);
$entities[$entity->id()][$langcode] = $entity;
}
$count_langcodes = count(array_unique($langcodes));
$view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
if ($count_langcodes > 1) {
// Render each entity separate if we do have more than one translation.
// @todo It should be possible to use viewMultiple even if you get
// more than one language. See https://drupal.org/node/2073217.
foreach ($entities as $entity_translation) {
foreach ($entity_translation as $langcode => $entity) {
$entity = $entity->getTranslation($langcode);
$this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $langcode);
}
}
}
else {
$langcode = reset($langcodes);
$entity_translations = array();
foreach ($entities as $entity_translation) {
$entity = $entity_translation[$langcode];
$entity_translations[$entity->id()] = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity;
}
$this->build = $view_builder->viewMultiple($entity_translations, $this->view->rowPlugin->options['view_mode'], $langcode);
}
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $row) {
$entity_id = $row->_entity->id();
$langcode = $this->getLangcode($row);
if (isset($this->build[$entity_id][$langcode])) {
return $this->build[$entity_id][$langcode];
}
else {
return $this->build[$entity_id];
}
}
/**
* Returns the language code associated to the given row.
*
* @param \Drupal\views\ResultRow $row
* The result row.
*
* @return string
* A language code.
*/
protected function getLangcode(ResultRow $row) {
return $row->_entity->getUntranslated()->language()->id;
}
}
<?php
/**
* @file
* Contains \Drupal\views\Entity\Render\RendererBase.
*/
namespace Drupal\views\Entity\Render;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Defines a base class for entity row renderers.
*/
abstract class RendererBase {
/**
* The view executable wrapping the view storage entity.
*
* @var \Drupal\views\ViewExecutable
*/
public $view = NULL;
/**
* The type of the entity being rendered.
*
* @var string
*/
protected $entityType;
/**
* Contains an array of render arrays, one for each rendered entity.
*
* @var array
*/
protected $build = array();
/**
* Constructs a renderer object.
*
* @param \Drupal\views\ViewExecutable $view
* The entity row being rendered.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*/
public function __construct(ViewExecutable $view, EntityTypeInterface $entity_type) {
$this->view = $view;
$this->entityType = $entity_type;
}
/**
* Alters the query if needed.
*
* @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
* The query to alter.
*/
public function query(QueryPluginBase $query) {
}
/**
* Runs before each row is rendered.
*
* @param $result
* The full array of results from the query.
*/
public function preRender(array $result) {
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
$entities = array();
/** @var \Drupal\views\ResultRow $row */
foreach ($result as $row) {
$entity = $row->_entity;
$entity->view = $this->view;
$entities[$entity->id()] = $entity;
}
$view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
$this->build = $view_builder->viewMultiple($entities, $this->view->rowPlugin->options['view_mode']);
}
/**
* Renders a row object.
*
* @param \Drupal\views\ResultRow $row
* A single row of the query result.
*
* @return array
* The renderable array of a single row.
*/
public function render(ResultRow $row) {
$entity_id = $row->_entity->id();
return $this->build[$entity_id];
}
}
<?php
/**
* @file
* Contains \Drupal\views\Entity\Render\TranslationLanguageRenderer.
*/
namespace Drupal\views\Entity\Render;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
/**
* Renders entity translations in their active language.
*/
class TranslationLanguageRenderer extends DefaultLanguageRenderer {
/**
* Stores the field alias of the langcode column.
*
* @var string
*/
protected $langcodeAlias;
/**
* {@inheritdoc}
*/
public function query(QueryPluginBase $query) {
// If the data table is defined, we use the translation language as render
// language, otherwise we fall back to the default entity language, which is
// stored in the revision table for revisionable entity types.
$entity_info = $this->view->rowPlugin->entityManager->getDefinition($this->entityType->id());
foreach (array('data_table', 'revision_table', 'base_table') as $key) {
if ($table = $entity_info->get($key)) {
$table_alias = $query->ensureTable($table);
$this->langcodeAlias = $query->addField($table_alias, 'langcode');
break;
}
}
}
/**
* {@inheritdoc}
*/
protected function getLangcode(ResultRow $row) {
return $row->{$this->langcodeAlias};
}
}
......@@ -8,7 +8,9 @@
namespace Drupal\views\Plugin\views\row;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\Container;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -52,22 +54,39 @@ class EntityRow extends RowPluginBase {
protected $entityType;
/**
* Contains an array of render arrays, one for each rendered entity.
* The renderer to be used to render the entity row.
*
* @var array
* @var \Drupal\views\Entity\Rendering\RendererBase
*/
protected $build = array();
protected $renderer;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
public $entityManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) {
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->languageManager = $language_manager;
}
/**
......@@ -86,7 +105,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'), $container->get('language_manager'));
}
/**
......@@ -96,6 +115,9 @@ protected function defineOptions() {
$options = parent::defineOptions();
$options['view_mode'] = array('default' => 'default');
// @todo Make the current language renderer the default as soon as we have a
// translation language filter. See https://drupal.org/node/2161845.
$options['rendering_language'] = array('default' => 'translation_language_renderer');
return $options;
}
......@@ -113,6 +135,15 @@ public function buildOptionsForm(&$form, &$form_state) {
'#title' => t('View mode'),
'#default_value' => $this->options['view_mode'],
);
$options = $this->buildRenderingLanguageOptions();
$form['rendering_language'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('Rendering language'),
'#default_value' => $this->options['rendering_language'],
'#access' => $this->languageManager->isMultilingual(),
);
}
/**
......@@ -128,6 +159,21 @@ protected function buildViewModeOptions() {
return $options;
}
/**
* Returns the available rendering strategies for language-aware entities.
*
* @return array
* An array of available entity row renderers keyed by renderer identifiers.
*/
protected function buildRenderingLanguageOptions() {
// @todo Consider making these plugins. See https://drupal.org/node/2173811.
return array(
'current_language_renderer' => $this->t('Current language'),
'default_language_renderer' => $this->t('Default language'),
'translation_language_renderer' => $this->t('Translation language'),
);
}
/**
* Overrides Drupal\views\Plugin\views\PluginBase::summaryTitle().
*/
......@@ -141,23 +187,35 @@ public function summaryTitle() {
}
}
/**
* Returns the current renderer.
*
* @return \Drupal\views\Entity\Render\RendererBase
* The configured renderer.
*/
protected function getRenderer() {
if (!isset($this->renderer)) {
$class = '\Drupal\views\Entity\Render\\' . Container::camelize($this->options['rendering_language']);
$this->renderer = new $class($this->view, $this->entityType);
}
return $this->renderer;
}
/**
* {@inheritdoc}
*/
public function query() {
parent::query();
$this->getRenderer()->query($this->view->getQuery());
}
/**
* {@inheritdoc}
*/
public function preRender($result) {
parent::preRender($result);
if ($result) {
// Get all entities which will be used to render in rows.
$entities = array();
foreach ($result as $row) {
$entity = $row->_entity;
$entity->view = $this->view;
$entities[$entity->id()] = $entity;
}
// Prepare the render arrays for all rows.
$this->build = entity_view_multiple($entities, $this->options['view_mode']);
$this->getRenderer()->preRender($result);
}
}
......@@ -165,7 +223,7 @@ public function preRender($result) {
* Overrides Drupal\views\Plugin\views\row\RowPluginBase::render().
*/
public function render($row) {
$entity_id = $row->{$this->field_alias};
return $this->build[$entity_id];
return $this->getRenderer()->render($row);
}
}
<?php
/**
* @file
* Contains \Drupal\views\Tests\Entity\RowEntityRenderersTest.
*/
namespace Drupal\views\Tests\Entity;
use Drupal\Core\Language\Language;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Views;
/**
* Tests the entity row renderers.
*
* @see \Drupal\views\Entity\Render\RendererBase
*/
class RowEntityRenderersTest extends ViewUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity', 'field', 'filter', 'text', 'node', 'user', 'language');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_entity_row_renderers');
/**
* An array of enabled languages.
*
* @var array
*/
protected $langcodes;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Entity: renderers',
'description' => 'Tests the entity row renderers.',
'group' => 'Views module integration',
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', array('node', 'node_revision', 'node_field_data', 'node_field_revision', 'node_access'));
$this->installSchema('user', array('users'));
$this->installConfig(array('node', 'language'));
$this->langcodes = array(\Drupal::languageManager()->getDefaultLanguage()->id);
for ($i = 0; $i < 2; $i++) {
$langcode = 'l' . $i;
$this->langcodes[] = $langcode;
$language = new Language(array('id' => $langcode));
language_save($language);
}
// Make sure we do not try to render non-existing user data.
$config = \Drupal::config('node.type.test');
$config->set('settings.node.submitted', FALSE);
$config->save();
}
/**
* Tests the entity row renderers.
*/
public function testRenderers() {
$values = array();
$controller = \Drupal::entityManager()->getStorageController('node');
$langcode_index = 0;
for ($i = 0; $i < count($this->langcodes); $i++) {
// Create a node with a different default language each time.
$default_langcode = $this->langcodes[$langcode_index++];
$node = $controller->create(array('type' => 'test', 'uid' => 0, 'langcode' => $default_langcode));
// Ensure the default language is processed first.
$langcodes = array_merge(array($default_langcode), array_diff($this->langcodes, array($default_langcode)));
foreach ($langcodes as $langcode) {
// Ensure we have a predictable result order.
$values[$i][$langcode] = $i . '-' . $langcode . '-' . $this->randomName();
if ($langcode != $default_langcode) {
$node->addTranslation($langcode, array('title' => $values[$i][$langcode]));
}
else {
$node->setTitle($values[$i][$langcode]);
}
$node->save();
}
}
$expected = array(
$values[0]['en'],
$values[0]['en'],
$values[0]['en'],
$values[1]['en'],
$values[1]['en'],
$values[1]['en'],
$values[2]['en'],
$values[2]['en'],
$values[2]['en'],
);
$this->assertTranslations('current_language_renderer', $expected, 'The current language renderer behaves as expected.');
$expected = array(
$values[0]['en'],
$values[0]['en'],
$values[0]['en'],
$values[1]['l0'],
$values[1]['l0'],
$values[1]['l0'],
$values[2]['l1'],
$values[2]['l1'],
$values[2]['l1'],
);
$this->assertTranslations('default_language_renderer', $expected, 'The default language renderer behaves as expected.');
$expected = array(
$values[0]['en'],
$values[0]['l0'],
$values[0]['l1'],
$values[1]['en'],
$values[1]['l0'],
$values[1]['l1'],
$values[2]['en'],
$values[2]['l0'],
$values[2]['l1'],
);
$this->assertTranslations('translation_language_renderer', $expected, 'The translation language renderer behaves as expected.');
}
/**
* Checks that the view results match the expected values.
*
* @param string $renderer_id
* The id of the renderer to be tested.
* @param array $expected
* An array of expected title translation values, one for each result row.
* @param string $message
* (optional) A message to display with the assertion.
* @param string $group
* (optional) The group this message is in.
*