diff --git a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml index 9ec9758185..bed13f225d 100644 --- a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml +++ b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml @@ -18,6 +18,10 @@ views.argument.taxonomy_index_tid_depth: type: boolean label: 'Use taxonomy term path' +views.argument.taxonomy_index_tid_depth_join: + type: views.argument.taxonomy_index_tid_depth + label: 'Taxonomy term ID with depth (using joins)' + views.argument.taxonomy_index_tid_depth_modifier: type: views_argument label: 'Taxonomy depth modifier' @@ -150,6 +154,10 @@ views.filter.taxonomy_index_tid_depth: type: integer label: 'Depth' +views.filter.taxonomy_index_tid_depth_join: + type: views.filter.taxonomy_index_tid_depth + label: 'Taxonomy term ID with depth (using joins)' + views.relationship.node_term_data: type: views_relationship label: 'Taxonomy term' diff --git a/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepthJoin.php b/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepthJoin.php index a2b6294adf..44b50797fb 100644 --- a/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepthJoin.php +++ b/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepthJoin.php @@ -49,9 +49,11 @@ public function query($group_by = FALSE) { // For every tid argument find all the children down to the depth set // in the options and save the tids for the condition. $tree = $this->termStorage->loadTree($term->getVocabularyId(), $term->id(), (int) $this->options['depth']); - $tids = array_merge($tids, array_map(function ($term) { - return $term->id(); - }, $tree)); + $tids_from_tree = []; + foreach ($tree as $item) { + $tids_from_tree[] = $item->tid; + } + $tids = array_merge($tids, $tids_from_tree); } } } diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepthJoin.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepthJoin.php index 7ec557d9de..42798e9eda 100644 --- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepthJoin.php +++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepthJoin.php @@ -1,10 +1,5 @@ value) ? $this->value : array($this->value); + $tids = is_array($this->value) ? $this->value : array($this->value); if ($this->options['depth'] > 0) { // When the depth is positive search the children. diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthJoinTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthJoinTest.php new file mode 100644 index 0000000000..89ce5253f7 --- /dev/null +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermArgumentDepthJoinTest.php @@ -0,0 +1,59 @@ +createTerm(['name' => 'First']); + + // Create a node w/o any terms. + $settings = ['type' => 'article']; + + // Create a node with linked to the term. + $settings['field_views_testing_tags'][0]['target_id'] = $first->id(); + $this->nodes[] = $this->drupalCreateNode($settings); + + $this->terms[0] = $first; + } + + /** + * Tests title escaping. + */ + public function testTermWithDepthArgumentTitleEscaping() { + $this->drupalGet('test_argument_taxonomy_index_tid_depth_join/' . $this->terms[0]->id()); + $this->assertEscaped($this->terms[0]->label()); + } + +} diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthJoinTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthJoinTest.php new file mode 100644 index 0000000000..9b268af0e8 --- /dev/null +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermFilterDepthJoinTest.php @@ -0,0 +1,136 @@ +createTerm(['name' => 'First']); + $second = $this->createTerm(['name' => 'Second', 'parent' => $first->id()]); + $third = $this->createTerm(['name' => 'Third', 'parent' => $second->id()]); + + // 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); + + $this->terms[0] = $first; + $this->terms[1] = $second; + $this->terms[2] = $third; + + $this->view = Views::getView('test_filter_taxonomy_index_tid_depth_join'); + } + + /** + * Tests the terms with depth filter. + */ + public function testTermWithDepthFilter() { + $column_map = ['nid' => 'nid']; + $assert_method = 'assertIdentical'; + + // 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], + ]; + $this->executeView($this->view); + $this->assertIdenticalResultsetHelper($this->view, $expected, $column_map, $assert_method); + + // Set filter to search on top-level term, with depth 0. + $expected = [['nid' => 4]]; + $this->assertTermWithDepthResult($this->terms[0]->id(), 0, $expected); + + // Top-level term, depth 1. + $expected = [['nid' => 4]]; + $this->assertTermWithDepthResult($this->terms[0]->id(), 0, $expected); + + // Top-level term, depth 2. + $expected = [['nid' => 4], ['nid' => 5]]; + $this->assertTermWithDepthResult($this->terms[0]->id(), 2, $expected); + + // Second-level term, depth 1. + $expected = [['nid' => 5]]; + $this->assertTermWithDepthResult($this->terms[1]->id(), 1, $expected); + + // Third-level term, depth 0. + $expected = [['nid' => 5]]; + $this->assertTermWithDepthResult($this->terms[2]->id(), 0, $expected); + + // Third-level term, depth 1. + $expected = [['nid' => 5]]; + $this->assertTermWithDepthResult($this->terms[2]->id(), 1, $expected); + + // Third-level term, depth -2. + $expected = [['nid' => 4], ['nid' => 5]]; + $this->assertTermWithDepthResult($this->terms[2]->id(), -2, $expected); + } + + /** + * Changes the tid filter to given term and depth. + * + * @param int $tid + * The term ID to filter on. + * @param int $depth + * The depth to search. + * @param array $expected + * The expected views result. + */ + protected function assertTermWithDepthResult($tid, $depth, array $expected) { + $this->view->destroy(); + $this->view->initDisplay(); + $filters = $this->view->displayHandlers->get('default') + ->getOption('filters'); + $filters['tid_depth']['depth'] = $depth; + $filters['tid_depth']['value'] = [$tid]; + $this->view->displayHandlers->get('default') + ->setOption('filters', $filters); + $this->executeView($this->view); + $this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical'); + } + +} diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_argument_taxonomy_index_tid_depth_join.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_argument_taxonomy_index_tid_depth_join.yml new file mode 100644 index 0000000000..64fa12b5c4 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_argument_taxonomy_index_tid_depth_join.yml @@ -0,0 +1,227 @@ +langcode: en +status: true +dependencies: + module: + - node + - taxonomy + - user +id: test_argument_taxonomy_index_tid_depth_join +label: test_argument_taxonomy_index_tid_depth_join +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + quantity: 9 + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + 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: { } + relationships: { } + arguments: + term_node_tid_depth: + id: term_node_tid_depth + table: node_field_data + field: term_node_tid_depth + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.term_node_tid_depth }}' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + depth: 0 + break_phrase: false + use_taxonomy_term_path: false + entity_type: node + plugin_id: taxonomy_index_tid_depth_join + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + cacheable: false + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test_argument_taxonomy_index_tid_depth_join + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + cacheable: false diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth_join.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth_join.yml new file mode 100644 index 0000000000..d225c6959d --- /dev/null +++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_filter_taxonomy_index_tid_depth_join.yml @@ -0,0 +1,178 @@ +langcode: en +status: true +dependencies: + module: + - node + - taxonomy + - user +id: test_filter_taxonomy_index_tid_depth_join +label: test_filter_taxonomy_index_tid_depth_join +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 1 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + quantity: 9 + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + plugin_id: field + entity_type: node + entity_field: title + filters: + status: + value: '1' + table: node_field_data + field: status + id: status + expose: + operator: '0' + group: 1 + plugin_id: boolean + entity_type: node + entity_field: status + tid_depth: + id: tid_depth + table: node_field_data + field: term_node_tid_depth + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + type: select + limit: true + vid: views_testing_tags + hierarchy: true + depth: -2 + error_message: true + plugin_id: taxonomy_index_tid_depth_join + sorts: { } + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } diff --git a/core/modules/views/views.module b/core/modules/views/views.module index e1a4d8ccc9..58b510fa8a 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -660,7 +660,7 @@ function views_query_views_alter(AlterableInterface $query) { // Replaces substitutions in tables. foreach ($tables as $table_name => $table_metadata) { foreach ($table_metadata['arguments'] as $replacement_key => $value) { - if (isset($substitutions[$value])) { + if (is_scalar($value) && isset($substitutions[$value])) { $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value]; } }