diff --git a/core/modules/views/src/Plugin/views/filter/ManyToOne.php b/core/modules/views/src/Plugin/views/filter/ManyToOne.php index e678dee82f3f9c38ee6be2f6059bc4a79ec477f4..86eb1f3361bae7edee38fbe98064c9c9447e0ef9 100644 --- a/core/modules/views/src/Plugin/views/filter/ManyToOne.php +++ b/core/modules/views/src/Plugin/views/filter/ManyToOne.php @@ -129,6 +129,9 @@ protected function opHelper() { if (empty($this->value)) { return; } + // Form API returns unchecked options in the form of option_id => 0. This + // breaks the generated query for "is all of" filters so we remove them. + $this->value = array_filter($this->value, 'static::arrayFilterZero'); $this->helper->addFilter(); } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml new file mode 100644 index 0000000000000000000000000000000000000000..26b1d3259ebdeaf39fc0262e8b4eb0815f53662f --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml @@ -0,0 +1,155 @@ +langcode: en +status: true +dependencies: + config: + - taxonomy.vocabulary.test_exposed_checkboxes + module: + - node + - taxonomy +id: test_exposed_form_checkboxes +label: '' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: tag + exposed_form: + options: + reset_button: true + type: basic + filters: + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: 'Content: Type' + description: 'Exposed description' + use_operator: false + operator: '' + identifier: type + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + plugin_id: in_operator + entity_type: node + entity_field: type + tid: + id: tid + table: taxonomy_index + field: tid + relationship: none + group_type: group + admin_label: '' + operator: and + value: { } + group: 1 + exposed: true + expose: + operator_id: tid_op + label: 'Has taxonomy term' + description: '' + use_operator: false + operator: tid_op + identifier: tid + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: test_exposed_checkboxes + hierarchy: false + error_message: true + plugin_id: taxonomy_index_tid + pager: + type: full + query: + options: + query_comment: '' + type: views_query + style: + type: default + row: + type: 'entity:node' + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + tags: { } + page_1: + display_options: + path: test_exposed_form_checkboxes + display_extenders: { } + display_plugin: page + display_title: Page + id: page_1 + position: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + tags: { } diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php new file mode 100644 index 0000000000000000000000000000000000000000..80584d796e2b79716a758c2b3597c22ee599297c --- /dev/null +++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php @@ -0,0 +1,168 @@ + 'test_exposed_checkboxes', + 'vid' => 'test_exposed_checkboxes', + 'nodes' => ['article' => 'article'], + ]); + $vocabulary->save(); + $this->vocabulary = $vocabulary; + + ViewTestData::createTestViews(self::class, ['views_test_config']); + $this->enableViewsTestModule(); + + // Create two content types. + $this->drupalCreateContentType(['type' => 'article']); + $this->drupalCreateContentType(['type' => 'page']); + + // Create some random nodes: 5 articles, one page. + for ($i = 0; $i < 5; $i++) { + $this->drupalCreateNode(['type' => 'article']); + } + $this->drupalCreateNode(['type' => 'page']); + } + + /** + * Tests overriding the default render option with checkboxes. + */ + public function testExposedFormRenderCheckboxes() { + // Use a test theme to convert multi-select elements into checkboxes. + \Drupal::service('theme_handler')->install(['views_test_checkboxes_theme']); + $this->config('system.theme') + ->set('default', 'views_test_checkboxes_theme') + ->save(); + + // Only display 5 items per page so we can test that paging works. + $view = Views::getView('test_exposed_form_checkboxes'); + $display = &$view->storage->getDisplay('default'); + $display['display_options']['pager']['options']['items_per_page'] = 5; + + $view->save(); + $this->drupalGet('test_exposed_form_checkboxes'); + + $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[article]"]'); + $this->assertEqual(count($actual), 1, 'Article option renders as a checkbox.'); + $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[page]"]'); + $this->assertEqual(count($actual), 1, 'Page option renders as a checkbox'); + + // Ensure that all results are displayed. + $rows = $this->xpath("//div[contains(@class, 'views-row')]"); + $this->assertEqual(count($rows), 5, '5 rows are displayed by default on the first page when no options are checked.'); + + $this->clickLink('Page 2'); + $rows = $this->xpath("//div[contains(@class, 'views-row')]"); + $this->assertEqual(count($rows), 1, '1 row is displayed by default on the second page when no options are checked.'); + $this->assertNoText('An illegal choice has been detected. Please contact the site administrator.'); + } + + /** + * Tests that "is all of" filters work with checkboxes. + */ + public function testExposedIsAllOfFilter() { + foreach (['Term 1', 'Term 2', 'Term 3'] as $term_name) { + // Add a few terms to the new vocabulary. + $term = Term::create([ + 'name' => $term_name, + 'vid' => $this->vocabulary->id(), + ]); + $term->save(); + $this->terms[] = $term; + } + + // Create a field. + $field_name = Unicode::strtolower($this->randomMachineName()); + $handler_settings = [ + 'target_bundles' => [ + $this->vocabulary->id() => $this->vocabulary->id(), + ], + 'auto_create' => FALSE, + ]; + $this->createEntityReferenceField('node', 'article', $field_name, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + + // Add some test nodes. + $this->createNode([ + 'type' => 'article', + $field_name => [$this->terms[0]->id(), $this->terms[1]->id()], + ]); + $this->createNode([ + 'type' => 'article', + $field_name => [$this->terms[0]->id(), $this->terms[2]->id()], + ]); + + // Use a test theme to convert multi-select elements into checkboxes. + \Drupal::service('theme_handler')->install(['views_test_checkboxes_theme']); + $this->config('system.theme') + ->set('default', 'views_test_checkboxes_theme') + ->save(); + + $this->drupalGet('test_exposed_form_checkboxes'); + + // Ensure that all results are displayed. + $rows = $this->xpath("//div[contains(@class, 'views-row')]"); + $this->assertEqual(count($rows), 8, 'All rows are displayed by default on the first page when no options are checked.'); + $this->assertNoText('An illegal choice has been detected. Please contact the site administrator.'); + + // Select one option and ensure we still have results. + $tid = $this->terms[0]->id(); + $this->drupalPostForm(NULL, ["tid[$tid]" => $tid], t('Apply')); + + // Ensure only nodes tagged with $tid are displayed. + $rows = $this->xpath("//div[contains(@class, 'views-row')]"); + $this->assertEqual(count($rows), 2, 'Correct rows are displayed when a tid is selected.'); + $this->assertNoText('An illegal choice has been detected. Please contact the site administrator.'); + } + +} diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php index 134f7b5458606d41bad85606643326d860cbaa47..220dc243533b3da5ecd3b2628a3c7fe524066b41 100644 --- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php +++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php @@ -191,48 +191,6 @@ public function testResetButton() { $this->helperButtonHasLabel('edit-reset', $expected_label); } - /** - * Tests overriding the default render option with checkboxes. - */ - public function testExposedFormRenderCheckboxes() { - // Make sure we have at least two options for node type. - $this->drupalCreateContentType(['type' => 'page']); - $this->drupalCreateNode(['type' => 'page']); - - // Use a test theme to convert multi-select elements into checkboxes. - \Drupal::service('theme_handler')->install(['views_test_checkboxes_theme']); - $this->config('system.theme') - ->set('default', 'views_test_checkboxes_theme') - ->save(); - - // Set the "type" filter to multi-select. - $view = Views::getView('test_exposed_form_buttons'); - $filter = $view->getHandler('page_1', 'filter', 'type'); - $filter['expose']['multiple'] = TRUE; - $view->setHandler('page_1', 'filter', 'type', $filter); - - // Only display 5 items per page so we can test that paging works. - $display = &$view->storage->getDisplay('default'); - $display['display_options']['pager']['options']['items_per_page'] = 5; - - $view->save(); - $this->drupalGet('test_exposed_form_buttons'); - - $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[article]"]'); - $this->assertEqual(count($actual), 1, 'Article option renders as a checkbox.'); - $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[page]"]'); - $this->assertEqual(count($actual), 1, 'Page option renders as a checkbox'); - - // Ensure that all results are displayed. - $rows = $this->xpath("//div[contains(@class, 'views-row')]"); - $this->assertEqual(count($rows), 5, '5 rows are displayed by default on the first page when no options are checked.'); - - $this->clickLink('Page 2'); - $rows = $this->xpath("//div[contains(@class, 'views-row')]"); - $this->assertEqual(count($rows), 1, '1 row is displayed by default on the second page when no options are checked.'); - $this->assertNoText('An illegal choice has been detected. Please contact the site administrator.'); - } - /** * Tests the exposed block functionality. */ diff --git a/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme index cab52ac713a2e123412027050073df2a0cf82dbd..393c6cbd1c5af3b819c1814aab90d23a26f0854e 100644 --- a/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme +++ b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme @@ -11,5 +11,10 @@ * Changes an exposed "type" filter from a multi-select to checkboxes. */ function views_test_checkboxes_theme_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) { - $form['type']['#type'] = 'checkboxes'; + if (isset($form['type'])) { + $form['type']['#type'] = 'checkboxes'; + } + if (isset($form['tid'])) { + $form['tid']['#type'] = 'checkboxes'; + } }