Loading core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php +3 −29 Changes for core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php: 3 added lines, 29 removed lines. Original line number Diff line number Diff line Loading @@ -2,10 +2,10 @@ namespace Drupal\taxonomy\Plugin\views\argument; use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\taxonomy\TaxonomyIndexDepthQueryTrait; use Drupal\views\Plugin\views\argument\ArgumentPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; Loading @@ -20,6 +20,7 @@ * @ViewsArgument("taxonomy_index_tid_depth") */ class IndexTidDepth extends ArgumentPluginBase implements ContainerFactoryPluginInterface { use TaxonomyIndexDepthQueryTrait; /** * @var \Drupal\Core\Entity\EntityStorageInterface Loading Loading @@ -101,40 +102,13 @@ public function query($group_by = FALSE) { if ($break->value === [-1]) { return FALSE; } $operator = (count($break->value) > 1) ? 'IN' : '='; $tids = $break->value; } else { $operator = "="; $tids = $this->argument; } // Now build the subqueries. $subquery = Database::getConnection()->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $where = ($this->view->query->getConnection()->condition('OR'))->condition('tn.tid', $tids, $operator); $last = "tn"; if ($this->options['depth'] > 0) { $subquery->leftJoin('taxonomy_term__parent', 'th', "[th].[entity_id] = [tn].[tid]"); $last = "th"; foreach (range(1, abs($this->options['depth'])) as $count) { $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[parent_target_id] = [th$count].[entity_id]"); $where->condition("th$count.entity_id", $tids, $operator); $last = "th$count"; } } elseif ($this->options['depth'] < 0) { foreach (range(1, abs($this->options['depth'])) as $count) { $field = $count == 1 ? 'tid' : 'entity_id'; $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[$field] = [th$count].[parent_target_id]"); $where->condition("th$count.entity_id", $tids, $operator); $last = "th$count"; } } $subquery->condition($where); $this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN'); $this->addSubQueryJoin($tids); } public function title() { Loading core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php +3 −31 Changes for core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php: 3 added lines, 31 removed lines. Original line number Diff line number Diff line Loading @@ -2,8 +2,8 @@ namespace Drupal\taxonomy\Plugin\views\filter; use Drupal\Core\Database\Database; use Drupal\Core\Form\FormStateInterface; use Drupal\taxonomy\TaxonomyIndexDepthQueryTrait; /** * Filter handler for taxonomy terms with depth. Loading @@ -16,6 +16,7 @@ * @ViewsFilter("taxonomy_index_tid_depth") */ class TaxonomyIndexTidDepth extends TaxonomyIndexTid { use TaxonomyIndexDepthQueryTrait; public function operatorOptions($which = 'title') { return [ Loading Loading @@ -52,10 +53,6 @@ public function query() { if (is_array($this->value)) { $this->value = current($this->value); } $operator = '='; } else { $operator = 'IN'; } // The normal use of ensureMyTable() here breaks Views. Loading @@ -70,32 +67,7 @@ public function query() { $this->tableAlias = $this->query->ensureTable($this->view->storage->get('base_table')); } // Now build the subqueries. $subquery = Database::getConnection()->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $where = ($this->view->query->getConnection()->condition('OR'))->condition('tn.tid', $this->value, $operator); $last = "tn"; if ($this->options['depth'] > 0) { $subquery->leftJoin('taxonomy_term__parent', 'th', "[th].[entity_id] = [tn].[tid]"); $last = "th"; foreach (range(1, abs($this->options['depth'])) as $count) { $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[parent_target_id] = [th$count].[entity_id]"); $where->condition("th$count.entity_id", $this->value, $operator); $last = "th$count"; } } elseif ($this->options['depth'] < 0) { foreach (range(1, abs($this->options['depth'])) as $count) { $field = $count == 1 ? 'tid' : 'entity_id'; $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[$field] = [th$count].[parent_target_id]"); $where->condition("th$count.entity_id", $this->value, $operator); $last = "th$count"; } } $subquery->condition($where); $this->query->addWhere($this->options['group'], "$this->tableAlias.$this->realField", $subquery, 'IN'); $this->addSubQueryJoin($this->value); } } core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php 0 → 100644 +94 −0 Changes for core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php: 94 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\taxonomy; use Drupal\views\Views; /** * Builds a performant depth subquery and adds it as a join to the query. * * This is performant because: * - It creates multiple queries on taxonomy_index with inner joins to * taxonomy_term__parent. These queries are combined together into a subquery * using unions to select all the node IDs with the terms in the hierarchy. * - It joins the resulting query to the main views query using an INNER JOIN. * * For example, if the $tids value is '718' and depth is configured to 2, the * resulting JOIN to node_field_data will be: * @code * INNER JOIN (SELECT tn.nid AS nid * FROM * taxonomy_index tn * WHERE tn.tid = '718' UNION SELECT tn.nid AS nid * FROM * taxonomy_index tn * INNER JOIN taxonomy_term__parent th ON tn.tid = th.entity_id * INNER JOIN taxonomy_term__parent th1 ON th.parent_target_id = th1.entity_id * WHERE th1.entity_id = '718' UNION SELECT tn.nid AS nid * FROM * taxonomy_index tn * INNER JOIN taxonomy_term__parent th ON tn.tid = th.entity_id * INNER JOIN taxonomy_term__parent th1 ON th.parent_target_id = th1.entity_id * INNER JOIN taxonomy_term__parent th2 ON th1.parent_target_id = th2.entity_id * WHERE th2.entity_id = '718') taxonomy_index_depth ON node_field_data.nid = taxonomy_index_depth.nid * @endcode */ trait TaxonomyIndexDepthQueryTrait { /** * Builds a performant depth subquery and adds it as a join to the query. * * @param string|array $tids * The terms ID(s) to do a depth search for. */ protected function addSubQueryJoin($tids): void { $connection = $this->query->getConnection(); $operator = is_array($tids) ? 'IN' : '='; // Create the depth 0 subquery. $subquery = $connection->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $subquery->condition('tn.tid', $tids, $operator); if ($this->options['depth'] !== 0) { // Set $left_field and $right_field depending on whether we are traversing // up or down the hierarchy. if ($this->options['depth'] > 0) { $left_field = 'parent_target_id'; $right_field = 'entity_id'; } else { $left_field = 'entity_id'; $right_field = 'parent_target_id'; } // Traverse the hierarchy to check the child or parent terms. foreach (range(1, abs($this->options['depth'])) as $count) { $union_query = $connection->select('taxonomy_index', 'tn'); $union_query->addField('tn', 'nid'); $left_join = "[tn].[tid]"; if ($this->options['depth'] > 0) { $union_query->join('taxonomy_term__parent', "th", "$left_join = [th].[entity_id]"); $left_join = "[th].[$left_field]"; } foreach (range(1, $count) as $inner_count) { $union_query->join('taxonomy_term__parent', "th$inner_count", "$left_join = [th$inner_count].[$right_field]"); $left_join = "[th$inner_count].[$left_field]"; } $union_query->condition("th$inner_count.entity_id", $tids, $operator); $subquery->union($union_query); } } // Add the subquery as a join. $definition['left_table'] = $this->tableAlias; $definition['left_field'] = $this->realField; $definition['field'] = 'nid'; $definition['type'] = 'INNER'; $definition['adjusted'] = TRUE; $definition['table formula'] = $subquery; $join = Views::pluginManager('join')->createInstance('standard', $definition); // There is no $base as we are joining to a query. $this->query->addRelationship('taxonomy_index_depth', $join, NULL, $this->relationship); } } core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml +16 −1 Changes for core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml: 16 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -169,7 +169,22 @@ display: depth: -2 error_message: true plugin_id: taxonomy_index_tid_depth sorts: { } sorts: created: id: created table: node_field_data field: created order: DESC entity_type: node entity_field: created plugin_id: date relationship: none group_type: group admin_label: '' exposed: false expose: label: '' granularity: second header: { } footer: { } empty: { } Loading core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermArgumentDepthTest.php 0 → 100644 +193 −0 Changes for core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermArgumentDepthTest.php: 193 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\Tests\taxonomy\Kernel\Views; use Drupal\views\Views; /** * Test the taxonomy term with depth argument. * * @group taxonomy */ class TaxonomyTermArgumentDepthTest extends TaxonomyTestBase { /** * {@inheritdoc} */ protected static $modules = [ 'taxonomy', 'taxonomy_test_views', 'views', 'node', ]; /** * Views IDs used by this test. * * @var string[] */ public static $testViews = ['test_argument_taxonomy_index_tid_depth']; /** * The terms used in the test. * * @var \Drupal\taxonomy\TermInterface[] */ protected $terms = []; /** * The view executable used in the test. * * @var \Drupal\views\ViewExecutable */ protected $view; /** * {@inheritdoc} */ protected function setUp($import_test_views = TRUE): void { parent::setUp($import_test_views); // Install node_access schema in order to successfully re-save nodes. $this->installSchema('node', ['node_access']); // Create a hierarchy 5 deep. Note the parent setup function creates two // top-level terms w/o children. $first = $this->createTerm(['name' => 'First']); $second = $this->createTerm(['name' => 'Second', 'parent' => $first->id()]); $third = $this->createTerm(['name' => 'Third', 'parent' => $second->id()]); $fourth = $this->createTerm(['name' => 'Fourth', 'parent' => $third->id()]); $fifth = $this->createTerm(['name' => 'Fifth', 'parent' => $fourth->id()]); $this->terms = [$first, $second, $third, $fourth, $fifth]; // Create a node w/o any terms. $settings = ['type' => 'article']; $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the top level term. $settings['field_views_testing_tags'][0]['target_id'] = $first->id(); $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the third level term. $settings['field_views_testing_tags'][0]['target_id'] = $third->id(); $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the fifth level term. $settings['field_views_testing_tags'][0]['target_id'] = $fifth->id(); $this->nodes[] = $this->drupalCreateNode($settings); $this->view = Views::getView(self::$testViews[0]); // Fix the created date to match the expectations of the order by in the // view. Node 1 should be the most recent node and node 6 should be the // oldest. $time = \Drupal::time(); foreach ($this->nodes as $i => $node) { $node->setCreatedTime($time->getRequestTime() - $i)->save(); } } /** * Tests the terms with depth filter. */ public function testTermWithDepthFilter(): void { // Default view has an empty value for this filter, so all nodes should be // returned. $expected = [ ['nid' => 1], ['nid' => 2], ['nid' => 3], ['nid' => 4], ['nid' => 5], ['nid' => 6], ]; $this->executeView($this->view); $this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical'); // Set filter to search on top-level term, with depth 0. $expected = [['nid' => 4]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 0); // Top-level term, depth 1. $expected = [['nid' => 4]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 1); // Top-level term, depth 2. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 2); // Top-level term, depth 9. $expected = [['nid' => 4], ['nid' => 5], ['nid' => 6]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 9); // Second-level term, depth 1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[1]->id(), 1); // Third-level term, depth 0. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), 0); // Third-level term, depth 1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), 1); // Third-level term, depth -2. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -2); // Third-level term, depth -9. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -9); // Fifth-level term, depth -9. $expected = [['nid' => 4], ['nid' => 5], ['nid' => 6]]; $this->assertTermWithDepthResult($expected, $this->terms[4]->id(), -9); // Third-level term, depth -1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -1); // Third-level and second-level term, depth -1, using a plus sign. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id() . '+' . $this->terms[1]->id(), -1, TRUE); // Third-level and second-level term, depth -1, using a comma. Note that due // to performance the "and" meaning of comma is not supported. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id() . ',' . $this->terms[1]->id(), -1, TRUE); // Top-level term and second level term, depth 1, using a plus sign. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id() . '+' . $this->terms[1]->id(), 1, TRUE); // Top-level term and second level term, depth 1, using a comma. Note that // due to performance the "and" meaning of comma is not supported. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id() . ',' . $this->terms[1]->id(), 1, TRUE); } /** * Asserts the result of the view for the given arguments. * * @param array $expected * The expected views result. * @param int|string $tid * The term ID or IDs to use as an argument. * @param int $depth * The depth to search. * @param bool $break_phrase * Whether to break the argument up into multiple terms. */ protected function assertTermWithDepthResult(array $expected, $tid, int $depth, bool $break_phrase = FALSE): void { $this->view->destroy(); $this->view->initDisplay(); $arguments = $this->view->displayHandlers->get('default')->getOption('arguments'); $arguments['term_node_tid_depth']['depth'] = $depth; $arguments['term_node_tid_depth']['break_phrase'] = $break_phrase; $this->view->displayHandlers->get('default')->setOption('arguments', $arguments); $this->executeView($this->view, [$tid]); $this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical'); } } Loading
core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php +3 −29 Changes for core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php: 3 added lines, 29 removed lines. Original line number Diff line number Diff line Loading @@ -2,10 +2,10 @@ namespace Drupal\taxonomy\Plugin\views\argument; use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\taxonomy\TaxonomyIndexDepthQueryTrait; use Drupal\views\Plugin\views\argument\ArgumentPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; Loading @@ -20,6 +20,7 @@ * @ViewsArgument("taxonomy_index_tid_depth") */ class IndexTidDepth extends ArgumentPluginBase implements ContainerFactoryPluginInterface { use TaxonomyIndexDepthQueryTrait; /** * @var \Drupal\Core\Entity\EntityStorageInterface Loading Loading @@ -101,40 +102,13 @@ public function query($group_by = FALSE) { if ($break->value === [-1]) { return FALSE; } $operator = (count($break->value) > 1) ? 'IN' : '='; $tids = $break->value; } else { $operator = "="; $tids = $this->argument; } // Now build the subqueries. $subquery = Database::getConnection()->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $where = ($this->view->query->getConnection()->condition('OR'))->condition('tn.tid', $tids, $operator); $last = "tn"; if ($this->options['depth'] > 0) { $subquery->leftJoin('taxonomy_term__parent', 'th', "[th].[entity_id] = [tn].[tid]"); $last = "th"; foreach (range(1, abs($this->options['depth'])) as $count) { $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[parent_target_id] = [th$count].[entity_id]"); $where->condition("th$count.entity_id", $tids, $operator); $last = "th$count"; } } elseif ($this->options['depth'] < 0) { foreach (range(1, abs($this->options['depth'])) as $count) { $field = $count == 1 ? 'tid' : 'entity_id'; $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[$field] = [th$count].[parent_target_id]"); $where->condition("th$count.entity_id", $tids, $operator); $last = "th$count"; } } $subquery->condition($where); $this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN'); $this->addSubQueryJoin($tids); } public function title() { Loading
core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php +3 −31 Changes for core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php: 3 added lines, 31 removed lines. Original line number Diff line number Diff line Loading @@ -2,8 +2,8 @@ namespace Drupal\taxonomy\Plugin\views\filter; use Drupal\Core\Database\Database; use Drupal\Core\Form\FormStateInterface; use Drupal\taxonomy\TaxonomyIndexDepthQueryTrait; /** * Filter handler for taxonomy terms with depth. Loading @@ -16,6 +16,7 @@ * @ViewsFilter("taxonomy_index_tid_depth") */ class TaxonomyIndexTidDepth extends TaxonomyIndexTid { use TaxonomyIndexDepthQueryTrait; public function operatorOptions($which = 'title') { return [ Loading Loading @@ -52,10 +53,6 @@ public function query() { if (is_array($this->value)) { $this->value = current($this->value); } $operator = '='; } else { $operator = 'IN'; } // The normal use of ensureMyTable() here breaks Views. Loading @@ -70,32 +67,7 @@ public function query() { $this->tableAlias = $this->query->ensureTable($this->view->storage->get('base_table')); } // Now build the subqueries. $subquery = Database::getConnection()->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $where = ($this->view->query->getConnection()->condition('OR'))->condition('tn.tid', $this->value, $operator); $last = "tn"; if ($this->options['depth'] > 0) { $subquery->leftJoin('taxonomy_term__parent', 'th', "[th].[entity_id] = [tn].[tid]"); $last = "th"; foreach (range(1, abs($this->options['depth'])) as $count) { $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[parent_target_id] = [th$count].[entity_id]"); $where->condition("th$count.entity_id", $this->value, $operator); $last = "th$count"; } } elseif ($this->options['depth'] < 0) { foreach (range(1, abs($this->options['depth'])) as $count) { $field = $count == 1 ? 'tid' : 'entity_id'; $subquery->leftJoin('taxonomy_term__parent', "th$count", "[$last].[$field] = [th$count].[parent_target_id]"); $where->condition("th$count.entity_id", $this->value, $operator); $last = "th$count"; } } $subquery->condition($where); $this->query->addWhere($this->options['group'], "$this->tableAlias.$this->realField", $subquery, 'IN'); $this->addSubQueryJoin($this->value); } }
core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php 0 → 100644 +94 −0 Changes for core/modules/taxonomy/src/TaxonomyIndexDepthQueryTrait.php: 94 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\taxonomy; use Drupal\views\Views; /** * Builds a performant depth subquery and adds it as a join to the query. * * This is performant because: * - It creates multiple queries on taxonomy_index with inner joins to * taxonomy_term__parent. These queries are combined together into a subquery * using unions to select all the node IDs with the terms in the hierarchy. * - It joins the resulting query to the main views query using an INNER JOIN. * * For example, if the $tids value is '718' and depth is configured to 2, the * resulting JOIN to node_field_data will be: * @code * INNER JOIN (SELECT tn.nid AS nid * FROM * taxonomy_index tn * WHERE tn.tid = '718' UNION SELECT tn.nid AS nid * FROM * taxonomy_index tn * INNER JOIN taxonomy_term__parent th ON tn.tid = th.entity_id * INNER JOIN taxonomy_term__parent th1 ON th.parent_target_id = th1.entity_id * WHERE th1.entity_id = '718' UNION SELECT tn.nid AS nid * FROM * taxonomy_index tn * INNER JOIN taxonomy_term__parent th ON tn.tid = th.entity_id * INNER JOIN taxonomy_term__parent th1 ON th.parent_target_id = th1.entity_id * INNER JOIN taxonomy_term__parent th2 ON th1.parent_target_id = th2.entity_id * WHERE th2.entity_id = '718') taxonomy_index_depth ON node_field_data.nid = taxonomy_index_depth.nid * @endcode */ trait TaxonomyIndexDepthQueryTrait { /** * Builds a performant depth subquery and adds it as a join to the query. * * @param string|array $tids * The terms ID(s) to do a depth search for. */ protected function addSubQueryJoin($tids): void { $connection = $this->query->getConnection(); $operator = is_array($tids) ? 'IN' : '='; // Create the depth 0 subquery. $subquery = $connection->select('taxonomy_index', 'tn'); $subquery->addField('tn', 'nid'); $subquery->condition('tn.tid', $tids, $operator); if ($this->options['depth'] !== 0) { // Set $left_field and $right_field depending on whether we are traversing // up or down the hierarchy. if ($this->options['depth'] > 0) { $left_field = 'parent_target_id'; $right_field = 'entity_id'; } else { $left_field = 'entity_id'; $right_field = 'parent_target_id'; } // Traverse the hierarchy to check the child or parent terms. foreach (range(1, abs($this->options['depth'])) as $count) { $union_query = $connection->select('taxonomy_index', 'tn'); $union_query->addField('tn', 'nid'); $left_join = "[tn].[tid]"; if ($this->options['depth'] > 0) { $union_query->join('taxonomy_term__parent', "th", "$left_join = [th].[entity_id]"); $left_join = "[th].[$left_field]"; } foreach (range(1, $count) as $inner_count) { $union_query->join('taxonomy_term__parent', "th$inner_count", "$left_join = [th$inner_count].[$right_field]"); $left_join = "[th$inner_count].[$left_field]"; } $union_query->condition("th$inner_count.entity_id", $tids, $operator); $subquery->union($union_query); } } // Add the subquery as a join. $definition['left_table'] = $this->tableAlias; $definition['left_field'] = $this->realField; $definition['field'] = 'nid'; $definition['type'] = 'INNER'; $definition['adjusted'] = TRUE; $definition['table formula'] = $subquery; $join = Views::pluginManager('join')->createInstance('standard', $definition); // There is no $base as we are joining to a query. $this->query->addRelationship('taxonomy_index_depth', $join, NULL, $this->relationship); } }
core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml +16 −1 Changes for core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth.yml: 16 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -169,7 +169,22 @@ display: depth: -2 error_message: true plugin_id: taxonomy_index_tid_depth sorts: { } sorts: created: id: created table: node_field_data field: created order: DESC entity_type: node entity_field: created plugin_id: date relationship: none group_type: group admin_label: '' exposed: false expose: label: '' granularity: second header: { } footer: { } empty: { } Loading
core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermArgumentDepthTest.php 0 → 100644 +193 −0 Changes for core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTermArgumentDepthTest.php: 193 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\Tests\taxonomy\Kernel\Views; use Drupal\views\Views; /** * Test the taxonomy term with depth argument. * * @group taxonomy */ class TaxonomyTermArgumentDepthTest extends TaxonomyTestBase { /** * {@inheritdoc} */ protected static $modules = [ 'taxonomy', 'taxonomy_test_views', 'views', 'node', ]; /** * Views IDs used by this test. * * @var string[] */ public static $testViews = ['test_argument_taxonomy_index_tid_depth']; /** * The terms used in the test. * * @var \Drupal\taxonomy\TermInterface[] */ protected $terms = []; /** * The view executable used in the test. * * @var \Drupal\views\ViewExecutable */ protected $view; /** * {@inheritdoc} */ protected function setUp($import_test_views = TRUE): void { parent::setUp($import_test_views); // Install node_access schema in order to successfully re-save nodes. $this->installSchema('node', ['node_access']); // Create a hierarchy 5 deep. Note the parent setup function creates two // top-level terms w/o children. $first = $this->createTerm(['name' => 'First']); $second = $this->createTerm(['name' => 'Second', 'parent' => $first->id()]); $third = $this->createTerm(['name' => 'Third', 'parent' => $second->id()]); $fourth = $this->createTerm(['name' => 'Fourth', 'parent' => $third->id()]); $fifth = $this->createTerm(['name' => 'Fifth', 'parent' => $fourth->id()]); $this->terms = [$first, $second, $third, $fourth, $fifth]; // Create a node w/o any terms. $settings = ['type' => 'article']; $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the top level term. $settings['field_views_testing_tags'][0]['target_id'] = $first->id(); $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the third level term. $settings['field_views_testing_tags'][0]['target_id'] = $third->id(); $this->nodes[] = $this->drupalCreateNode($settings); // Create a node with only the fifth level term. $settings['field_views_testing_tags'][0]['target_id'] = $fifth->id(); $this->nodes[] = $this->drupalCreateNode($settings); $this->view = Views::getView(self::$testViews[0]); // Fix the created date to match the expectations of the order by in the // view. Node 1 should be the most recent node and node 6 should be the // oldest. $time = \Drupal::time(); foreach ($this->nodes as $i => $node) { $node->setCreatedTime($time->getRequestTime() - $i)->save(); } } /** * Tests the terms with depth filter. */ public function testTermWithDepthFilter(): void { // Default view has an empty value for this filter, so all nodes should be // returned. $expected = [ ['nid' => 1], ['nid' => 2], ['nid' => 3], ['nid' => 4], ['nid' => 5], ['nid' => 6], ]; $this->executeView($this->view); $this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical'); // Set filter to search on top-level term, with depth 0. $expected = [['nid' => 4]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 0); // Top-level term, depth 1. $expected = [['nid' => 4]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 1); // Top-level term, depth 2. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 2); // Top-level term, depth 9. $expected = [['nid' => 4], ['nid' => 5], ['nid' => 6]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id(), 9); // Second-level term, depth 1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[1]->id(), 1); // Third-level term, depth 0. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), 0); // Third-level term, depth 1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), 1); // Third-level term, depth -2. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -2); // Third-level term, depth -9. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -9); // Fifth-level term, depth -9. $expected = [['nid' => 4], ['nid' => 5], ['nid' => 6]]; $this->assertTermWithDepthResult($expected, $this->terms[4]->id(), -9); // Third-level term, depth -1. $expected = [['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id(), -1); // Third-level and second-level term, depth -1, using a plus sign. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id() . '+' . $this->terms[1]->id(), -1, TRUE); // Third-level and second-level term, depth -1, using a comma. Note that due // to performance the "and" meaning of comma is not supported. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[2]->id() . ',' . $this->terms[1]->id(), -1, TRUE); // Top-level term and second level term, depth 1, using a plus sign. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id() . '+' . $this->terms[1]->id(), 1, TRUE); // Top-level term and second level term, depth 1, using a comma. Note that // due to performance the "and" meaning of comma is not supported. $expected = [['nid' => 4], ['nid' => 5]]; $this->assertTermWithDepthResult($expected, $this->terms[0]->id() . ',' . $this->terms[1]->id(), 1, TRUE); } /** * Asserts the result of the view for the given arguments. * * @param array $expected * The expected views result. * @param int|string $tid * The term ID or IDs to use as an argument. * @param int $depth * The depth to search. * @param bool $break_phrase * Whether to break the argument up into multiple terms. */ protected function assertTermWithDepthResult(array $expected, $tid, int $depth, bool $break_phrase = FALSE): void { $this->view->destroy(); $this->view->initDisplay(); $arguments = $this->view->displayHandlers->get('default')->getOption('arguments'); $arguments['term_node_tid_depth']['depth'] = $depth; $arguments['term_node_tid_depth']['break_phrase'] = $break_phrase; $this->view->displayHandlers->get('default')->setOption('arguments', $arguments); $this->executeView($this->view, [$tid]); $this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical'); } }