summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2017-07-03 20:45:44 (GMT)
committerNathaniel Catchpole2017-07-03 20:45:44 (GMT)
commit3469863334d1404ed17952e1a061f2344f52a267 (patch)
treef375e2eacad1e8ac67197c389af454ffbcae529d
parentf3f18a23c8a1a24787390036ad76e0d274fd9f7a (diff)
Issue #2451657 by Upchuk, tstoeckler, k4v, lokapujya, dawehner, willwh, holist, yobottehg, Peacog, Sharique, plach, Berdir, deepakaryan1988, Xano, geertvd, pminf, alexpott, AkshayKalose, lauriii, mkernel, pritish.kumar, jibran: Views should not condition joins on the langcode of fields that are not translatable
-rw-r--r--core/modules/views/src/Plugin/views/field/EntityField.php5
-rw-r--r--core/modules/views/src/Plugin/views/join/FieldOrLanguageJoin.php106
-rw-r--r--core/modules/views/src/Plugin/views/join/JoinPluginBase.php192
-rw-r--r--core/modules/views/src/Tests/FieldApiDataTest.php244
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml187
-rw-r--r--core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml86
-rw-r--r--core/modules/views/tests/src/Kernel/Handler/SortTranslationTest.php152
-rw-r--r--core/modules/views/tests/src/Kernel/Plugin/FieldOrLanguageJoinTest.php210
-rw-r--r--core/modules/views/views.module28
-rw-r--r--core/modules/views/views.views.inc81
10 files changed, 1204 insertions, 87 deletions
diff --git a/core/modules/views/src/Plugin/views/field/EntityField.php b/core/modules/views/src/Plugin/views/field/EntityField.php
index 25b0a55..f580e50 100644
--- a/core/modules/views/src/Plugin/views/field/EntityField.php
+++ b/core/modules/views/src/Plugin/views/field/EntityField.php
@@ -1043,10 +1043,13 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
*/
public function getValue(ResultRow $values, $field = NULL) {
$entity = $this->getEntity($values);
+ // Retrieve the translated object.
+ $translated_entity = $this->getEntityFieldRenderer()->getEntityTranslation($entity, $values);
+
// Some bundles might not have a specific field, in which case the entity
// (potentially a fake one) doesn't have it either.
/** @var \Drupal\Core\Field\FieldItemListInterface $field_item_list */
- $field_item_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']} : NULL;
+ $field_item_list = isset($translated_entity->{$this->definition['field_name']}) ? $translated_entity->{$this->definition['field_name']} : NULL;
if (!isset($field_item_list)) {
// There isn't anything we can do without a valid field.
diff --git a/core/modules/views/src/Plugin/views/join/FieldOrLanguageJoin.php b/core/modules/views/src/Plugin/views/join/FieldOrLanguageJoin.php
new file mode 100644
index 0000000..44734d4
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/join/FieldOrLanguageJoin.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\views\Plugin\views\join;
+
+use Drupal\Core\Database\Query\SelectInterface;
+
+/**
+ * Implementation for the "field OR language" join.
+ *
+ * If the extra conditions contain either ".langcode" or ".bundle", they will be
+ * grouped and joined with OR instead of AND. The entire group will then be
+ * joined to the other conditions with AND.
+ *
+ * This is needed for configurable fields that are translatable on some bundles
+ * and untranslatable on others. The correct field values to fetch in this case
+ * have a langcode that matches the entity record *or* have a bundle on which
+ * the field is untranslatable. Thus, the entity base table (or data table, or
+ * revision data table, respectively) must join the field data table (or field
+ * revision table) on a matching langcode *or* a bundle where the field is
+ * untranslatable. The following example views data achieves this for a node
+ * field named 'field_tags' which is translatable on an 'article' node type, but
+ * not on the 'news' and 'page' node types:
+ *
+ * @code
+ * $data['node__field_tags']['table']['join']['node_field_data'] = [
+ * 'join_id' => 'field_or_language_join',
+ * 'table' => 'node__field_tags',
+ * 'left_field' => 'nid',
+ * 'field' => 'entity_id',
+ * 'extra' => [
+ * [
+ * 'field' => 'deleted',
+ * 'value' => 0,
+ * 'numeric' => TRUE,
+ * ],
+ * [
+ * 'left_field' => 'langcode',
+ * 'field' => 'langcode',
+ * ],
+ * [
+ * 'field' => 'bundle',
+ * 'value' => ['news', 'page'],
+ * ],
+ * ],
+ * ];
+ * @endcode
+ *
+ * The resulting join condition for this example would be the following:
+ *
+ * @code
+ * ON node__field_tags.deleted = 0
+ * AND (
+ * node_field_data.langcode = node__field_tags.langcode
+ * OR node__field.tags.bundle IN ['news', 'page']
+ * )
+ * @endcode
+ *
+ * @see views_field_default_views_data()
+ *
+ * @ingroup views_join_handlers
+ *
+ * @ViewsJoin("field_or_language_join")
+ */
+class FieldOrLanguageJoin extends JoinPluginBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterface $select_query, $left_table = NULL) {
+ if (empty($this->extra)) {
+ return;
+ }
+
+ if (is_array($this->extra)) {
+ $extras = [];
+ foreach ($this->extra as $extra) {
+ $extras[] = $this->buildExtra($extra, $arguments, $table, $select_query, $left_table);
+ }
+
+ // Remove and store the langcode OR bundle join condition extra.
+ $language_bundle_conditions = [];
+ foreach ($extras as $key => $extra) {
+ if (strpos($extra, '.langcode') !== FALSE || strpos($extra, '.bundle') !== FALSE) {
+ $language_bundle_conditions[] = $extra;
+ unset($extras[$key]);
+ }
+ }
+
+ if (count($extras) > 1) {
+ $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
+ }
+ elseif ($extras) {
+ $condition .= ' AND ' . array_shift($extras);
+ }
+
+ // Tack on the langcode OR bundle join condition extra.
+ if (!empty($language_bundle_conditions)) {
+ $condition .= ' AND (' . implode(' OR ', $language_bundle_conditions) . ')';
+ }
+ }
+ elseif (is_string($this->extra)) {
+ $condition .= " AND ($this->extra)";
+ }
+ }
+
+}
diff --git a/core/modules/views/src/Plugin/views/join/JoinPluginBase.php b/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
index 0aa6aa3..ccf40f6 100644
--- a/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
+++ b/core/modules/views/src/Plugin/views/join/JoinPluginBase.php
@@ -2,6 +2,7 @@
namespace Drupal\views\Plugin\views\join;
+use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Plugin\PluginBase;
/**
@@ -261,12 +262,13 @@ class JoinPluginBase extends PluginBase implements JoinPluginInterface {
}
if ($this->leftTable) {
- $left = $view_query->getTableInfo($this->leftTable);
- $left_field = "$left[alias].$this->leftField";
+ $left_table = $view_query->getTableInfo($this->leftTable);
+ $left_field = "$left_table[alias].$this->leftField";
}
else {
// This can be used if left_field is a formula or something. It should be used only *very* rarely.
$left_field = $this->leftField;
+ $left_table = NULL;
}
$condition = "$left_field = $table[alias].$this->field";
@@ -274,89 +276,123 @@ class JoinPluginBase extends PluginBase implements JoinPluginInterface {
// Tack on the extra.
if (isset($this->extra)) {
- if (is_array($this->extra)) {
- $extras = [];
- foreach ($this->extra as $info) {
- // Do not require 'value' to be set; allow for field syntax instead.
- $info += [
- 'value' => NULL,
- ];
- // Figure out the table name. Remember, only use aliases provided
- // if at all possible.
- $join_table = '';
- if (!array_key_exists('table', $info)) {
- $join_table = $table['alias'] . '.';
- }
- elseif (isset($info['table'])) {
- // If we're aware of a table alias for this table, use the table
- // alias instead of the table name.
- if (isset($left) && $left['table'] == $info['table']) {
- $join_table = $left['alias'] . '.';
- }
- else {
- $join_table = $info['table'] . '.';
- }
- }
+ $this->joinAddExtra($arguments, $condition, $table, $select_query, $left_table);
+ }
- // Convert a single-valued array of values to the single-value case,
- // and transform from IN() notation to = notation
- if (is_array($info['value']) && count($info['value']) == 1) {
- $info['value'] = array_shift($info['value']);
- }
- if (is_array($info['value'])) {
- // We use an SA-CORE-2014-005 conformant placeholder for our array
- // of values. Also, note that the 'IN' operator is implicit.
- // @see https://www.drupal.org/node/2401615.
- $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
- $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
- $placeholder_sql = "( $placeholder )";
- }
- else {
- // With a single value, the '=' operator is implicit.
- $operator = !empty($info['operator']) ? $info['operator'] : '=';
- $placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
- }
- // Set 'field' as join table field if available or set 'left field' as
- // join table field is not set.
- if (isset($info['field'])) {
- $join_table_field = "$join_table$info[field]";
- // Allow the value to be set either with the 'value' element or
- // with 'left_field'.
- if (isset($info['left_field'])) {
- $placeholder_sql = "$left[alias].$info[left_field]";
- }
- else {
- $arguments[$placeholder] = $info['value'];
- }
- }
- // Set 'left field' as join table field is not set.
- else {
- $join_table_field = "$left[alias].$info[left_field]";
- $arguments[$placeholder] = $info['value'];
- }
- // Render out the SQL fragment with parameters.
- $extras[] = "$join_table_field $operator $placeholder_sql";
- }
+ $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
+ }
+ /**
+ * Adds the extras to the join condition.
+ *
+ * @param array $arguments
+ * Array of query arguments.
+ * @param string $condition
+ * The condition to be built.
+ * @param array $table
+ * The right table.
+ * @param \Drupal\Core\Database\Query\SelectInterface $select_query
+ * The current select query being built.
+ * @param array $left_table
+ * The left table.
+ */
+ protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterface $select_query, $left_table = NULL) {
+ if (is_array($this->extra)) {
+ $extras = [];
+ foreach ($this->extra as $info) {
+ $extras[] = $this->buildExtra($info, $arguments, $table, $select_query, $left_table);
+ }
- if ($extras) {
- if (count($extras) == 1) {
- $condition .= ' AND ' . array_shift($extras);
- }
- else {
- $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
- }
+ if ($extras) {
+ if (count($extras) == 1) {
+ $condition .= ' AND ' . array_shift($extras);
+ }
+ else {
+ $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
}
}
- elseif ($this->extra && is_string($this->extra)) {
- $condition .= " AND ($this->extra)";
+ }
+ elseif ($this->extra && is_string($this->extra)) {
+ $condition .= " AND ($this->extra)";
+ }
+ }
+
+ /**
+ * Builds a single extra condition.
+ *
+ * @param array $info
+ * The extra information. See JoinPluginBase::$extra for details.
+ * @param array $arguments
+ * Array of query arguments.
+ * @param array $table
+ * The right table.
+ * @param \Drupal\Core\Database\Query\SelectInterface $select_query
+ * The current select query being built.
+ * @param array $left
+ * The left table.
+ *
+ * @return string
+ * The extra condition
+ */
+ protected function buildExtra($info, &$arguments, $table, SelectInterface $select_query, $left) {
+ // Do not require 'value' to be set; allow for field syntax instead.
+ $info += [
+ 'value' => NULL,
+ ];
+ // Figure out the table name. Remember, only use aliases provided
+ // if at all possible.
+ $join_table = '';
+ if (!array_key_exists('table', $info)) {
+ $join_table = $table['alias'] . '.';
+ }
+ elseif (isset($info['table'])) {
+ // If we're aware of a table alias for this table, use the table
+ // alias instead of the table name.
+ if (isset($left) && $left['table'] == $info['table']) {
+ $join_table = $left['alias'] . '.';
+ }
+ else {
+ $join_table = $info['table'] . '.';
}
}
- $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
+ // Convert a single-valued array of values to the single-value case,
+ // and transform from IN() notation to = notation
+ if (is_array($info['value']) && count($info['value']) == 1) {
+ $info['value'] = array_shift($info['value']);
+ }
+ if (is_array($info['value'])) {
+ // We use an SA-CORE-2014-005 conformant placeholder for our array
+ // of values. Also, note that the 'IN' operator is implicit.
+ // @see https://www.drupal.org/node/2401615.
+ $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
+ $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
+ $placeholder_sql = "( $placeholder )";
+ }
+ else {
+ // With a single value, the '=' operator is implicit.
+ $operator = !empty($info['operator']) ? $info['operator'] : '=';
+ $placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
+ }
+ // Set 'field' as join table field if available or set 'left field' as
+ // join table field is not set.
+ if (isset($info['field'])) {
+ $join_table_field = "$join_table$info[field]";
+ // Allow the value to be set either with the 'value' element or
+ // with 'left_field'.
+ if (isset($info['left_field'])) {
+ $placeholder_sql = "$left[alias].$info[left_field]";
+ }
+ else {
+ $arguments[$placeholder] = $info['value'];
+ }
+ }
+ // Set 'left field' as join table field is not set.
+ else {
+ $join_table_field = "$left[alias].$info[left_field]";
+ $arguments[$placeholder] = $info['value'];
+ }
+ // Render out the SQL fragment with parameters.
+ return "$join_table_field $operator $placeholder_sql";
}
}
-
-/**
- * @}
- */
diff --git a/core/modules/views/src/Tests/FieldApiDataTest.php b/core/modules/views/src/Tests/FieldApiDataTest.php
index 0e15695..246a324 100644
--- a/core/modules/views/src/Tests/FieldApiDataTest.php
+++ b/core/modules/views/src/Tests/FieldApiDataTest.php
@@ -5,6 +5,11 @@ namespace Drupal\views\Tests;
use Drupal\Component\Render\MarkupInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Tests\Views\FieldTestBase;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\language\Entity\ContentLanguageSettings;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\views\Views;
/**
* Tests the Field Views data.
@@ -13,10 +18,27 @@ use Drupal\field\Tests\Views\FieldTestBase;
*/
class FieldApiDataTest extends FieldTestBase {
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = ['language'];
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $testViews = ['test_field_config_translation_filter'];
+
+ /**
+ * The nodes used by the translation filter tests.
+ *
+ * @var \Drupal\node\NodeInterface[]
+ */
+ protected $translationNodes;
+
protected function setUp() {
- parent::setUp();
+ parent::setUp(FALSE);
- $field_names = $this->setUpFieldStorages(1);
+ $field_names = $this->setUpFieldStorages(4);
// Attach the field to nodes only.
$field = [
@@ -43,6 +65,109 @@ class FieldApiDataTest extends FieldTestBase {
];
$nodes[] = $this->drupalCreateNode($edit);
}
+
+ $bundles = [];
+ $bundles[] = $bundle = NodeType::create(['type' => 'bundle1']);
+ $bundle->save();
+ $bundles[] = $bundle = NodeType::create(['type' => 'bundle2']);
+ $bundle->save();
+
+ // Make the first field translatable on all bundles.
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[1],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[0]->id(),
+ 'translatable' => TRUE,
+ ]);
+ $field->save();
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[1],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[1]->id(),
+ 'translatable' => TRUE,
+ ]);
+ $field->save();
+
+ // Make the second field not translatable on any bundle.
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[2],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[0]->id(),
+ 'translatable' => FALSE,
+ ]);
+ $field->save();
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[2],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[1]->id(),
+ 'translatable' => FALSE,
+ ]);
+ $field->save();
+
+ // Make the last field translatable on some bundles.
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[3],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[0]->id(),
+ 'translatable' => TRUE,
+ ]);
+ $field->save();
+ $field = FieldConfig::create([
+ 'field_name' => $field_names[3],
+ 'entity_type' => 'node',
+ 'bundle' => $bundles[1]->id(),
+ 'translatable' => FALSE,
+ ]);
+ $field->save();
+
+ // Create some example content.
+ ConfigurableLanguage::create([
+ 'id' => 'es',
+ ])->save();
+ ConfigurableLanguage::create([
+ 'id' => 'fr',
+ ])->save();
+
+ $config = ContentLanguageSettings::loadByEntityTypeBundle('node', $bundles[0]->id());
+ $config->setDefaultLangcode('es')
+ ->setLanguageAlterable(TRUE)
+ ->save();
+ $config = ContentLanguageSettings::loadByEntityTypeBundle('node', $bundles[1]->id());
+ $config->setDefaultLangcode('es')
+ ->setLanguageAlterable(TRUE)
+ ->save();
+
+ $node = Node::create([
+ 'title' => 'Test title ' . $bundles[0]->id(),
+ 'type' => $bundles[0]->id(),
+ 'langcode' => 'es',
+ $field_names[1] => 'field name 1: es',
+ $field_names[2] => 'field name 2: es',
+ $field_names[3] => 'field name 3: es',
+ ]);
+ $node->save();
+ $this->translationNodes[] = $node;
+ $translation = $node->addTranslation('fr');
+ $translation->{$field_names[1]}->value = 'field name 1: fr';
+ $translation->{$field_names[3]}->value = 'field name 3: fr';
+ $translation->title->value = $node->title->value;
+ $translation->save();
+
+ $node = Node::create([
+ 'title' => 'Test title ' . $bundles[1]->id(),
+ 'type' => $bundles[1]->id(),
+ 'langcode' => 'es',
+ $field_names[1] => 'field name 1: es',
+ $field_names[2] => 'field name 2: es',
+ $field_names[3] => 'field name 3: es',
+ ]);
+ $node->save();
+ $this->translationNodes[] = $node;
+ $translation = $node->addTranslation('fr');
+ $translation->{$field_names[1]}->value = 'field name 1: fr';
+ $translation->title->value = $node->title->value;
+ $translation->save();
+
}
/**
@@ -137,4 +262,119 @@ class FieldApiDataTest extends FieldTestBase {
return $data;
}
+ /**
+ * Tests filtering entries with different translatabilty.
+ */
+ public function testEntityFieldFilter() {
+ $map = [
+ 'nid' => 'nid',
+ 'langcode' => 'langcode',
+ ];
+
+ $view = Views::getView('test_field_config_translation_filter');
+
+ // Filter by 'field name 1: es'.
+ $view->setDisplay('embed_1');
+ $this->executeView($view);
+ $expected = [
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'es',
+ ],
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'es',
+ ],
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ // Filter by 'field name 1: fr'.
+ $view->setDisplay('embed_2');
+ $this->executeView($view);
+ $expected = [
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'fr',
+ ],
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'fr',
+ ],
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ // Filter by 'field name 2: es'.
+ $view->setDisplay('embed_3');
+ $this->executeView($view);
+ $expected = [
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'es',
+ ],
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'fr',
+ ],
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'es',
+ ],
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'fr',
+ ],
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ // Filter by 'field name 2: fr', which doesn't exist.
+ $view->setDisplay('embed_4');
+ $this->executeView($view);
+ $expected = [
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ // Filter by 'field name 3: es'.
+ $view->setDisplay('embed_5');
+ $this->executeView($view);
+ $expected = [
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'es',
+ ],
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'es',
+ ],
+ // Why is this one returned?
+ [
+ 'nid' => $this->translationNodes[1]->id(),
+ 'langcode' => 'fr',
+ ],
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ // Filter by 'field name 3: fr'.
+ $view->setDisplay('embed_6');
+ $this->executeView($view);
+ $expected = [
+ [
+ 'nid' => $this->translationNodes[0]->id(),
+ 'langcode' => 'fr',
+ ],
+ ];
+
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+ }
+
}
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml
new file mode 100644
index 0000000..2202d9c
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml
@@ -0,0 +1,187 @@
+langcode: en
+status: true
+dependencies: { }
+id: test_field_config_translation_filter
+module: views
+description: ''
+tag: ''
+base_table: node_field_data
+base_field: id
+core: '8'
+display:
+ default:
+ display_options:
+ access:
+ type: none
+ cache:
+ type: none
+ fields:
+ nid:
+ id: nid
+ field: nid
+ table: node_field_data
+ plugin_id: field
+ entity_type: node
+ entity_field: nid
+ langcode:
+ id: langcode
+ field: langcode
+ table: node_field_data
+ plugin_id: field
+ entity_type: node
+ entity_field: langcode
+ field_name_1:
+ id: field_name_1
+ table: node__field_name_1
+ field: field_name_1
+ plugin_id: field
+ entity_type: node
+ entity_field: field_name_1
+ field_name_2:
+ id: field_name_2
+ table: node__field_name_2
+ field: field_name_2
+ plugin_id: field
+ entity_type: node
+ entity_field: field_name_2
+ field_name_3:
+ id: field_name_3
+ table: node__field_name_3
+ field: field_name_3
+ plugin_id: field
+ entity_type: node
+ entity_field: field_name_3
+ sorts:
+ nid:
+ id: nid
+ table: node_field_data
+ field: nid
+ order: ASC
+ plugin_id: standard
+ entity_type: node
+ entity_field: nid
+ langcode:
+ id: langcode
+ table: node_field_data
+ field: langcode
+ relationship: none
+ group_type: group
+ admin_label: ''
+ order: ASC
+ exposed: false
+ expose:
+ label: ''
+ entity_type: node
+ entity_field: langcode
+ plugin_id: standard
+ style:
+ type: html_list
+ row:
+ type: fields
+ display_plugin: default
+ display_title: Master
+ id: default
+ position: 0
+ embed_1:
+ display_options:
+ defaults:
+ fields: true
+ filters: false
+ filters:
+ field_name_1_value:
+ id: field_name_1_value
+ table: node__field_name_1
+ field: field_name_1_value
+ value: 'field name 1: es'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_1
+ display_plugin: embed
+ display_title: Embed 1
+ id: embed_1
+ position: 1
+ embed_2:
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ field_name_1_value:
+ id: field_name_1_value
+ table: node__field_name_1
+ field: field_name_1_value
+ value: 'field name 1: fr'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_1
+ display_plugin: embed
+ display_title: Embed 2
+ id: embed_2
+ position: 2
+ embed_3:
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ field_name_2_value:
+ id: field_name_2_value
+ table: node__field_name_2
+ field: field_name_2_value
+ value: 'field name 2: es'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_2
+ display_plugin: embed
+ display_title: Embed 3
+ id: embed_3
+ position: 3
+ embed_4:
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ field_name_2_value:
+ id: field_name_2_value
+ table: node__field_name_2
+ field: field_name_2_value
+ value: 'field name 2: fr'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_2
+ display_plugin: embed
+ display_title: Embed 4
+ id: embed_4
+ position: 4
+ embed_5:
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ field_name_3_value:
+ id: field_name_3_value
+ table: node__field_name_3
+ field: field_name_3_value
+ value: 'field name 3: es'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_3
+ display_plugin: embed
+ display_title: Embed 5
+ id: embed_5
+ position: 5
+ embed_6:
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ field_name_3_value:
+ id: field_name_3_value
+ table: node__field_name_3
+ field: field_name_3_value
+ value: 'field name 3: fr'
+ plugin_id: string
+ entity_type: node
+ entity_field: field_name_3
+ display_plugin: embed
+ display_title: Embed 6
+ id: embed_6
+ position: 6
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml
new file mode 100644
index 0000000..09b9e41
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml
@@ -0,0 +1,86 @@
+langcode: en
+status: true
+dependencies: { }
+id: test_view_sort_translation
+module: views
+description: ''
+tag: ''
+base_table: node_field_data
+base_field: id
+core: '8'
+display:
+ default:
+ display_options:
+ fields:
+ nid:
+ id: nid
+ field: nid
+ table: node_field_data
+ plugin_id: field
+ entity_type: node
+ entity_field: nid
+ langcode:
+ id: langcode
+ field: langcode
+ table: node_field_data
+ plugin_id: field
+ entity_type: node
+ entity_field: langcode
+ weight:
+ id: weight
+ table: node__weight
+ field: weight
+ plugin_id: numeric
+ entity_type: node
+ entity_field: weight
+ filters:
+ langcode:
+ id: langcode
+ table: node_field_data
+ field: langcode
+ relationship: none
+ group_type: group
+ admin_label: ''
+ operator: in
+ value:
+ 'en': 'en'
+ group: 1
+ exposed: false
+ entity_type: node
+ entity_field: langcode
+ plugin_id: language
+ sorts:
+ weight:
+ id: weight
+ table: node__weight
+ field: weight_value
+ order: ASC
+ plugin_id: standard
+ entity_type: node
+ entity_field: weight
+ display_plugin: default
+ display_title: Master
+ id: default
+ position: 0
+ display_de:
+ display_plugin: embed
+ id: display_de
+ display_options:
+ defaults:
+ filters: false
+ filters:
+ langcode:
+ id: langcode
+ table: node_field_data
+ field: langcode
+ relationship: none
+ group_type: group
+ admin_label: ''
+ operator: in
+ value:
+ 'de': 'de'
+ group: 1
+ exposed: false
+ entity_type: node
+ entity_field: langcode
+ plugin_id: language
diff --git a/core/modules/views/tests/src/Kernel/Handler/SortTranslationTest.php b/core/modules/views/tests/src/Kernel/Handler/SortTranslationTest.php
new file mode 100644
index 0000000..549761d
--- /dev/null
+++ b/core/modules/views/tests/src/Kernel/Handler/SortTranslationTest.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Drupal\Tests\views\Kernel\Handler;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
+use Drupal\views\Views;
+
+/**
+ * Tests sorting on translatable and not translatable fields.
+ *
+ * @group views
+ */
+class SortTranslationTest extends ViewsKernelTestBase {
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = [
+ 'node',
+ 'field',
+ 'language',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $testViews = [
+ 'test_view_sort_translation',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp($import_test_views = TRUE) {
+ parent::setUp($import_test_views);
+ ConfigurableLanguage::createFromLangcode('de')->save();
+ $this->installSchema('node', 'node_access');
+ $this->installEntitySchema('node');
+ $this->installEntitySchema('user');
+
+ //$this->installConfig('node');
+ $this->container->get('kernel')->rebuildContainer();
+
+ $node_type = NodeType::create(['type' => 'article']);
+ $node_type->save();
+
+ FieldStorageConfig::create([
+ 'field_name' => 'text',
+ 'entity_type' => 'node',
+ 'type' => 'string',
+ ])->save();
+
+ FieldConfig::create([
+ 'field_name' => 'text',
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'label' => 'Translated text',
+ 'translatable' => TRUE
+ ])->save();
+
+ FieldStorageConfig::create([
+ 'field_name' => 'weight',
+ 'entity_type' => 'node',
+ 'type' => 'integer',
+ ])->save();
+
+ FieldConfig::create([
+ 'field_name' => 'weight',
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'translatable' => FALSE,
+ ])->save();
+
+ for ($i = 0; $i < 3; $i++) {
+ $node = Node::create([
+ 'type' => 'article',
+ 'title' => 'Title en ' . $i,
+ 'weight' => ['value' => 3 - $i],
+ 'text' => ['value' => 'moo en ' . $i],
+ 'langcode' => 'en',
+ ]);
+ $node->save();
+
+ $translation = $node->addTranslation('de');
+ $translation->title->value = 'Title DE ' . $i;
+ $translation->text->value = 'moo DE ' . $i;
+ $translation->save();
+ $nodes[] = $node;
+ }
+ }
+
+ /**
+ * Test sorting on an untranslated field.
+ */
+ public function testSortbyUntranslatedIntegerField() {
+ $map = [
+ 'nid' => 'nid',
+ 'node_field_data_langcode' => 'langcode',
+ ];
+
+ $view = Views::getView('test_view_sort_translation');
+ $view->setDisplay('default');
+ $this->executeView($view);
+
+ // With ascending sort, the nodes should come out in reverse order.
+ $expected = [
+ [
+ 'nid' => 3,
+ 'langcode' => 'en',
+ ],
+ [
+ 'nid' => 2,
+ 'langcode' => 'en',
+ ],
+ [
+ 'nid' => 1,
+ 'langcode' => 'en',
+ ],
+ ];
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+
+ $view = Views::getView('test_view_sort_translation');
+ $view->setDisplay('display_de');
+ $this->executeView($view);
+
+ $expected = [
+ [
+ 'nid' => 3,
+ 'langcode' => 'de',
+ ],
+ [
+ 'nid' => 2,
+ 'langcode' => 'de',
+ ],
+ [
+ 'nid' => 1,
+ 'langcode' => 'de',
+ ],
+ ];
+
+ // The weight field is not translated, we sort by it so the nodes
+ // should come out in the same order in both languages.
+ $this->assertIdenticalResultset($view, $expected, $map);
+ $view->destroy();
+ }
+
+}
diff --git a/core/modules/views/tests/src/Kernel/Plugin/FieldOrLanguageJoinTest.php b/core/modules/views/tests/src/Kernel/Plugin/FieldOrLanguageJoinTest.php
new file mode 100644
index 0000000..1f9c6da
--- /dev/null
+++ b/core/modules/views/tests/src/Kernel/Plugin/FieldOrLanguageJoinTest.php
@@ -0,0 +1,210 @@
+<?php
+
+namespace Drupal\Tests\views\Kernel\Plugin;
+
+use Drupal\views\Plugin\views\join\FieldOrLanguageJoin;
+use Drupal\views\Views;
+
+
+/**
+ * Tests the "field OR language" join plugin.
+ *
+ * @group views
+ * @see \Drupal\views\Plugin\views\join\FieldOrLanguageJoin
+ */
+class FieldOrLanguageJoinTest extends RelationshipJoinTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $testViews = ['test_view'];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $pluginId = 'field_or_language_join';
+
+ /**
+ * A plugin manager which handlers the instances of joins.
+ *
+ * @var \Drupal\views\Plugin\ViewsPluginManager
+ */
+ protected $manager;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp($import_test_views = TRUE) {
+ parent::setUp();
+
+ // Add a join plugin manager which can be used in all of the tests.
+ $this->manager = $this->container->get('plugin.manager.views.join');
+ }
+
+ /**
+ * Tests base join functionality.
+ *
+ * This duplicates parts of
+ * \Drupal\Tests\views\Kernel\Plugin\JoinTest::testBasePlugin() to ensure that
+ * no functionality provided by the base join plugin is broken.
+ */
+ public function testBase() {
+ // Setup a simple join and test the result sql.
+ $view = Views::getView('test_view');
+ $view->initDisplay();
+ $view->initQuery();
+
+ // First define a simple join without an extra condition.
+ // Set the various options on the join object.
+ $configuration = [
+ 'left_table' => 'views_test_data',
+ 'left_field' => 'uid',
+ 'table' => 'users_field_data',
+ 'field' => 'uid',
+ 'adjusted' => TRUE,
+ ];
+ $join = $this->manager->createInstance($this->pluginId, $configuration);
+ $this->assertTrue($join instanceof FieldOrLanguageJoin);
+ $this->assertNull($join->extra);
+ $this->assertTrue($join->adjusted);
+
+ $join_info = $this->buildJoin($view, $configuration, 'users_field_data');
+ $this->assertSame($join_info['join type'], 'LEFT');
+ $this->assertSame($join_info['table'], $configuration['table']);
+ $this->assertSame($join_info['alias'], 'users_field_data');
+ $this->assertSame($join_info['condition'], 'views_test_data.uid = users_field_data.uid');
+
+ // Set a different alias and make sure table info is as expected.
+ $join_info = $this->buildJoin($view, $configuration, 'users1');
+ $this->assertSame($join_info['alias'], 'users1');
+
+ // Set a different join type (INNER) and make sure it is used.
+ $configuration['type'] = 'INNER';
+ $join_info = $this->buildJoin($view, $configuration, 'users2');
+ $this->assertSame($join_info['join type'], 'INNER');
+
+ // Setup addition conditions and make sure it is used.
+ $random_name_1 = $this->randomMachineName();
+ $random_name_2 = $this->randomMachineName();
+ $configuration['extra'] = [
+ [
+ 'field' => 'name',
+ 'value' => $random_name_1
+ ],
+ [
+ 'field' => 'name',
+ 'value' => $random_name_2,
+ 'operator' => '<>'
+ ],
+ ];
+ $join_info = $this->buildJoin($view, $configuration, 'users3');
+ $this->assertContains('views_test_data.uid = users3.uid', $join_info['condition']);
+ $this->assertContains('users3.name = :views_join_condition_0', $join_info['condition']);
+ $this->assertContains('users3.name <> :views_join_condition_1', $join_info['condition']);
+ $this->assertSame(array_values($join_info['arguments']), [$random_name_1, $random_name_2]);
+
+ // Test that 'IN' conditions are properly built.
+ $random_name_1 = $this->randomMachineName();
+ $random_name_2 = $this->randomMachineName();
+ $random_name_3 = $this->randomMachineName();
+ $random_name_4 = $this->randomMachineName();
+ $configuration['extra'] = [
+ [
+ 'field' => 'name',
+ 'value' => $random_name_1
+ ],
+ [
+ 'field' => 'name',
+ 'value' => [$random_name_2, $random_name_3, $random_name_4],
+ ],
+ ];
+ $join_info = $this->buildJoin($view, $configuration, 'users4');
+ $this->assertContains('views_test_data.uid = users4.uid', $join_info['condition']);
+ $this->assertContains('users4.name = :views_join_condition_0', $join_info['condition']);
+ $this->assertContains('users4.name IN ( :views_join_condition_1[] )', $join_info['condition']);
+ $this->assertSame($join_info['arguments'][':views_join_condition_1[]'], [$random_name_2, $random_name_3, $random_name_4]);
+ }
+
+ /**
+ * Tests the adding of conditions by the join plugin.
+ */
+ public function testLanguageBundleConditions() {
+ // Setup a simple join and test the result sql.
+ $view = Views::getView('test_view');
+ $view->initDisplay();
+ $view->initQuery();
+
+ // Set the various options on the join object with only a langcode
+ // condition.
+ $configuration = [
+ 'table' => 'node__field_tags',
+ 'left_table' => 'node',
+ 'left_field' => 'nid',
+ 'field' => 'entity_id',
+ 'extra' => [
+ [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ],
+ ],
+ ];
+ $join_info = $this->buildJoin($view, $configuration, 'node__field_tags');
+ $this->assertContains('AND (node__field_tags.langcode = .langcode)', $join_info['condition']);
+
+ array_unshift($configuration['extra'], [
+ 'field' => 'deleted',
+ 'value' => 0,
+ 'numeric' => TRUE,
+ ]);
+ $join_info = $this->buildJoin($view, $configuration, 'node__field_tags');
+ $this->assertContains('AND (node__field_tags.langcode = .langcode)', $join_info['condition']);
+
+ // Replace the language condition with a bundle condition.
+ $configuration['extra'][1] = [
+ 'field' => 'bundle',
+ 'value' => ['page'],
+ ];
+ $join_info = $this->buildJoin($view, $configuration, 'node__field_tags');
+ $this->assertContains('AND (node__field_tags.bundle = :views_join_condition_1)', $join_info['condition']);
+
+ // Now re-add a language condition to make sure the bundle and language
+ // conditions are combined with an OR.
+ $configuration['extra'][] = [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ];
+ $join_info = $this->buildJoin($view, $configuration, 'node__field_tags');
+ $this->assertContains('AND (node__field_tags.bundle = :views_join_condition_1 OR node__field_tags.langcode = .langcode)', $join_info['condition']);
+ }
+
+ /**
+ * Builds a join using the given configuration.
+ *
+ * @param \Drupal\views\ViewExecutable $view
+ * The view used in this test.
+ * @param $configuration
+ * The join plugin configuration.
+ * @param $table_alias
+ * The table alias to use for the join.
+ *
+ * @return array
+ * The join information for the joined table. See
+ * \Drupal\Core\Database\Query\Select::$tables for more information on the
+ * structure of the array.
+ */
+ protected function buildJoin($view, $configuration, $table_alias) {
+ // Build the actual join values and read them back from the query object.
+ $query = \Drupal::database()->select('node');
+
+ $join = $this->manager->createInstance('field_or_language_join', $configuration);
+ $this->assertInstanceOf(FieldOrLanguageJoin::class, $join, 'The correct join class got loaded.');
+
+ $table = ['alias' => $table_alias];
+ $join->buildJoin($query, $table, $view->query);
+
+ $tables = $query->getTables();
+ $join_info = $tables[$table['alias']];
+ return $join_info;
+ }
+
+}
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 453c90b..6b51029 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -17,7 +17,6 @@ use Drupal\views\ViewExecutable;
use Drupal\views\Entity\View;
use Drupal\views\Render\ViewsRenderPipelineMarkup;
use Drupal\views\Views;
-use Drupal\field\FieldConfigInterface;
/**
* Implements hook_help().
@@ -437,21 +436,42 @@ function views_add_contextual_links(&$render_element, $location, $display_id, ar
/**
* Implements hook_ENTITY_TYPE_insert() for 'field_config'.
*/
-function views_field_config_insert(FieldConfigInterface $field) {
+function views_field_config_insert(EntityInterface $field) {
Views::viewsData()->clear();
}
/**
* Implements hook_ENTITY_TYPE_update() for 'field_config'.
*/
-function views_field_config_update(FieldConfigInterface $field) {
+function views_field_config_update(EntityInterface $entity) {
Views::viewsData()->clear();
}
/**
* Implements hook_ENTITY_TYPE_delete() for 'field_config'.
*/
-function views_field_config_delete(FieldConfigInterface $field) {
+function views_field_config_delete(EntityInterface $entity) {
+ Views::viewsData()->clear();
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_insert().
+ */
+function views_base_field_override_insert(EntityInterface $entity) {
+ Views::viewsData()->clear();
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update().
+ */
+function views_base_field_override_update(EntityInterface $entity) {
+ Views::viewsData()->clear();
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_delete().
+ */
+function views_base_field_override_delete(EntityInterface $entity) {
Views::viewsData()->clear();
}
diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
index 0bd5f27..ed6fa07 100644
--- a/core/modules/views/views.views.inc
+++ b/core/modules/views/views.views.inc
@@ -9,6 +9,7 @@ use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Render\Markup;
+use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\system\ActionConfigEntityInterface;
@@ -352,6 +353,49 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
];
}
+ // Determine if the fields are translatable.
+ $bundles_names = $field_storage->getBundles();
+ $translation_join_type = FALSE;
+ $fields = [];
+ $translatable_configs = [];
+ $untranslatable_configs = [];
+ $untranslatable_config_bundles = [];
+
+ foreach ($bundles_names as $bundle) {
+ $fields[$bundle] = FieldConfig::loadByName($entity_type->id(), $bundle, $field_name);
+ }
+ foreach ($fields as $bundle => $config_entity) {
+ if (!empty($config_entity)) {
+ if ($config_entity->isTranslatable()) {
+ $translatable_configs[$bundle] = $config_entity;
+ }
+ else {
+ $untranslatable_configs[$bundle] = $config_entity;
+ }
+ }
+ else {
+ // https://www.drupal.org/node/2451657#comment-11462881
+ \Drupal::logger('views')->error(
+ t('A non-existent config entity name returned by FieldStorageConfigInterface::getBundles(): field name: %field, bundle: %bundle',
+ ['%field' => $field_name, '%bundle' => $bundle]
+ ));
+ }
+ }
+
+ // If the field is translatable on all the bundles, there will be a join on
+ // the langcode.
+ if (!empty($translatable_configs) && empty($untranslatable_configs)) {
+ $translation_join_type = 'language';
+ }
+ // If the field is translatable only on certain bundles, there will be a join
+ // on langcode OR bundle name.
+ elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) {
+ foreach ($untranslatable_configs as $config) {
+ $untranslatable_config_bundles[] = $config->getTargetBundle();
+ }
+ $translation_join_type = 'language_bundle';
+ }
+
// Build the relationships between the field table and the entity tables.
$table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
if ($data_table) {
@@ -362,7 +406,6 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
'field' => 'entity_id',
'extra' => [
['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
- ['left_field' => 'langcode', 'field' => 'langcode'],
],
];
}
@@ -378,6 +421,24 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
];
}
+ if ($translation_join_type === 'language_bundle') {
+ $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
+ $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ];
+ $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+ 'field' => 'bundle',
+ 'value' => $untranslatable_config_bundles,
+ ];
+ }
+ elseif ($translation_join_type === 'language') {
+ $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ];
+ }
+
if ($supports_revisions) {
$table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
if ($entity_revision_data_table) {
@@ -388,7 +449,6 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
'field' => 'revision_id',
'extra' => [
['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
- ['left_field' => 'langcode', 'field' => 'langcode'],
],
];
}
@@ -403,6 +463,23 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora
],
];
}
+ if ($translation_join_type === 'language_bundle') {
+ $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
+ $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ];
+ $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
+ 'value' => $untranslatable_config_bundles,
+ 'field' => 'bundle',
+ ];
+ }
+ elseif ($translation_join_type === 'language') {
+ $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
+ 'left_field' => 'langcode',
+ 'field' => 'langcode',
+ ];
+ }
}
$group_name = $entity_type->getLabel();