diff --git a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php index c6d3e39405aeb9dd221523b8b924b4e28df43106..143077d0848495b0d481fb27fc7159b3e29393bc 100644 --- a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php +++ b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php @@ -246,6 +246,31 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { } } + // Check if there are exposed filters for this view. + $exposed_filters = []; + $exposed_required_filters = []; + foreach ($this->view->filter as $id => $handler) { + if ($handler->canExpose() && $handler->isExposed() && !empty($handler->options['expose']['identifier'])) { + if ($handler->options['expose']['required'] && $handler->options['plugin_id'] !== 'boolean') { + $exposed_required_filters[$handler->options['expose']['identifier']] = $id; + } + $exposed_filters[$handler->options['expose']['identifier']] = $id; + } + } + + // Do not auto process the form if there are any required exposed filters + // that do not have input. + if (!empty($exposed_required_filters)) { + $form_values = $form_state->getUserInput(); + foreach ($exposed_required_filters as $key => $required_filter) { + if (empty($form_values[$key])) { + $form_state->setAlwaysProcess(FALSE); + break; + } + } + } + $all_exposed = array_merge($exposed_sorts, $exposed_filters); + if (!empty($this->options['reset_button'])) { $form['actions']['reset'] = [ '#value' => $this->options['reset_button_label'], @@ -253,15 +278,6 @@ public function exposedFormAlter(&$form, FormStateInterface $form_state) { '#weight' => 10, ]; - // Get an array of exposed filters, keyed by identifier option. - $exposed_filters = []; - foreach ($this->view->filter as $id => $handler) { - if ($handler->canExpose() && $handler->isExposed() && !empty($handler->options['expose']['identifier'])) { - $exposed_filters[$handler->options['expose']['identifier']] = $id; - } - } - $all_exposed = array_merge($exposed_sorts, $exposed_filters); - // Set the access to FALSE if there is no exposed input. if (!array_intersect_key($all_exposed, $this->view->getExposedInput())) { $form['actions']['reset']['#access'] = FALSE; diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_required_filters.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_required_filters.yml new file mode 100644 index 0000000000000000000000000000000000000000..b7355861ac88377c6eee8286ba12576c3e1263e8 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_required_filters.yml @@ -0,0 +1,83 @@ +langcode: en +status: true +dependencies: + module: + - node +id: test_exposed_form_required_filters +label: '' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + id: default + display_title: Default + display_plugin: default + position: 0 + display_options: + pager: + type: full + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + access: + type: none + cache: + type: tag + filters: + title: + id: title + table: node_field_data + field: title + relationship: none + entity_type: node + entity_field: title + plugin_id: string + operator: '=' + value: '' + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + identifier: title + required: true + type: + expose: + identifier: type + label: 'Content: Type' + operator_id: type_op + reduce: false + description: 'Exposed description' + required: false + exposed: true + field: type + id: type + table: node_field_data + plugin_id: in_operator + entity_type: node + entity_field: type + style: + type: default + row: + type: 'entity:node' + query: + type: views_query + options: + query_comment: '' + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 0 + display_options: + path: test_exposed_form_required_filters diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php index 78cc60399ef8d77696285f082f3e2182cb77f458..81372402c99b0c6fdb719aaa589e65bf87c81807 100644 --- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php +++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php @@ -38,6 +38,7 @@ class ExposedFormTest extends ViewTestBase { 'test_exposed_form_sort_items_per_page', 'test_exposed_form_pager', 'test_remember_selected', + 'test_exposed_form_required_filters', ]; /** @@ -399,6 +400,50 @@ protected function testTextInputRequired(): void { $this->assertSession()->pageTextNotContains($on_demand_text); } + /** + * Tests required exposed filters. + */ + public function testRequiredExposedFilters(): void { + $this->drupalGet('test_exposed_form_required_filters'); + $this->assertSession()->statusCodeEquals(200); + + // Ensure that no error element is shown when a text filter is required. + $this->assertSession()->elementNotExists('css', '.messages--error'); + $this->assertFalse($this->getSession()->getPage()->findField('title')->hasClass('error')); + + $view = Views::getView('test_exposed_form_required_filters'); + $display = &$view->storage->getDisplay('default'); + + // Turn off required text filter. + $display['display_options']['filters']['title']['expose']['required'] = FALSE; + $view->save(); + + $this->drupalGet('test_exposed_form_required_filters'); + $this->assertSession()->statusCodeEquals(200); + + // Ensure that results are displayed by default when no input is provided. + $this->assertSession()->elementsCount('xpath', "//div[contains(@class, 'views-row')]", 10); + + $view = Views::getView('test_exposed_form_required_filters'); + $display = &$view->storage->getDisplay('default'); + + // Turn on required type filter. + $display['display_options']['filters']['type']['expose']['required'] = TRUE; + $display['display_options']['filters']['type']['value'] = [ + 'article' => 'article', + 'page' => 'page', + ]; + $view->save(); + + $this->drupalGet('test_exposed_form_required_filters'); + $this->assertSession()->statusCodeEquals(200); + + // Ensure that results are filtered by default when input is required, since + // the first filter will be selected by default if "Select all" is not an + // option. + $this->assertSession()->elementsCount('xpath', "//div[contains(@class, 'views-row')]", 5); + } + /** * Tests exposed forms with exposed sort and items per page. */