3 namespace Drupal\views\Plugin\views\exposed_form;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Cache\CacheableDependencyInterface;
8 use Drupal\Core\Form\FormState;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\views\Plugin\views\PluginBase;
13 * Base class for Views exposed filter form plugins.
15 * @ingroup views_exposed_form_plugins
17 abstract class ExposedFormPluginBase extends PluginBase implements CacheableDependencyInterface, ExposedFormPluginInterface {
22 protected $usesOptions = TRUE;
27 protected function defineOptions() {
28 $options = parent::defineOptions();
29 $options['submit_button'] = ['default' => $this->t('Apply')];
30 $options['reset_button'] = ['default' => FALSE];
31 $options['reset_button_label'] = ['default' => $this->t('Reset')];
32 $options['exposed_sorts_label'] = ['default' => $this->t('Sort by')];
33 $options['expose_sort_order'] = ['default' => TRUE];
34 $options['sort_asc_label'] = ['default' => $this->t('Asc')];
35 $options['sort_desc_label'] = ['default' => $this->t('Desc')];
42 public function buildOptionsForm(&$form, FormStateInterface $form_state) {
43 parent::buildOptionsForm($form, $form_state);
44 $form['submit_button'] = [
45 '#type' => 'textfield',
46 '#title' => $this->t('Submit button text'),
47 '#default_value' => $this->options['submit_button'],
51 $form['reset_button'] = [
52 '#type' => 'checkbox',
53 '#title' => $this->t('Include reset button (resets all applied exposed filters)'),
54 '#default_value' => $this->options['reset_button'],
57 $form['reset_button_label'] = [
58 '#type' => 'textfield',
59 '#title' => $this->t('Reset button label'),
60 '#description' => $this->t('Text to display in the reset button of the exposed form.'),
61 '#default_value' => $this->options['reset_button_label'],
65 'input[name="exposed_form_options[reset_button]"]' => ['checked' => FALSE],
70 $form['exposed_sorts_label'] = [
71 '#type' => 'textfield',
72 '#title' => $this->t('Exposed sorts label'),
73 '#default_value' => $this->options['exposed_sorts_label'],
77 $form['expose_sort_order'] = [
78 '#type' => 'checkbox',
79 '#title' => $this->t('Allow people to choose the sort order'),
80 '#description' => $this->t('If sort order is not exposed, the sort criteria settings for each sort will determine its order.'),
81 '#default_value' => $this->options['expose_sort_order'],
84 $form['sort_asc_label'] = [
85 '#type' => 'textfield',
86 '#title' => $this->t('Label for ascending sort'),
87 '#default_value' => $this->options['sort_asc_label'],
91 'input[name="exposed_form_options[expose_sort_order]"]' => ['checked' => TRUE],
96 $form['sort_desc_label'] = [
97 '#type' => 'textfield',
98 '#title' => $this->t('Label for descending sort'),
99 '#default_value' => $this->options['sort_desc_label'],
103 'input[name="exposed_form_options[expose_sort_order]"]' => ['checked' => TRUE],
112 public function renderExposedForm($block = FALSE) {
113 // Deal with any exposed filters we may have, before building.
114 $form_state = (new FormState())
116 'view' => $this->view,
117 'display' => &$this->view->display_handler->display,
124 // Some types of displays (eg. attachments) may wish to use the exposed
125 // filters of their parent displays instead of showing an additional
126 // exposed filter form for the attachment as well as that for the parent.
127 if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) {
128 $form_state->set('rerender', NULL);
131 if (!empty($this->ajax)) {
132 $form_state->set('ajax', TRUE);
135 $form = \Drupal::formBuilder()->buildForm('\Drupal\views\Form\ViewsExposedForm', $form_state);
136 $errors = $form_state->getErrors();
138 // If the exposed form had errors, do not build the view.
139 if (!empty($errors)) {
140 $this->view->build_info['abort'] = TRUE;
143 if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) {
154 public function query() {
156 $exposed_data = isset($view->exposed_data) ? $view->exposed_data : [];
157 $sort_by = isset($exposed_data['sort_by']) ? $exposed_data['sort_by'] : NULL;
158 if (!empty($sort_by)) {
159 // Make sure the original order of sorts is preserved
160 // (e.g. a sticky sort is often first)
161 if (isset($view->sort[$sort_by])) {
162 $view->query->orderby = [];
163 foreach ($view->sort as $key => $sort) {
164 if (!$sort->isExposed()) {
167 elseif ($key == $sort_by) {
168 if (isset($exposed_data['sort_order']) && in_array($exposed_data['sort_order'], ['ASC', 'DESC'])) {
169 $sort->options['order'] = $exposed_data['sort_order'];
171 $sort->setRelationship();
182 public function preRender($values) { }
187 public function postRender(&$output) { }
192 public function preExecute() { }
197 public function postExecute() { }
202 public function exposedFormAlter(&$form, FormStateInterface $form_state) {
203 if (!empty($this->options['submit_button'])) {
204 $form['actions']['submit']['#value'] = $this->options['submit_button'];
207 // Check if there is exposed sorts for this view
209 foreach ($this->view->sort as $id => $handler) {
210 if ($handler->canExpose() && $handler->isExposed()) {
211 $exposed_sorts[$id] = Html::escape($handler->options['expose']['label']);
215 if (count($exposed_sorts)) {
218 '#options' => $exposed_sorts,
219 '#title' => $this->options['exposed_sorts_label'],
222 'ASC' => $this->options['sort_asc_label'],
223 'DESC' => $this->options['sort_desc_label'],
225 $user_input = $form_state->getUserInput();
226 if (isset($user_input['sort_by']) && isset($this->view->sort[$user_input['sort_by']])) {
227 $default_sort_order = $this->view->sort[$user_input['sort_by']]->options['order'];
230 $first_sort = reset($this->view->sort);
231 $default_sort_order = $first_sort->options['order'];
234 if (!isset($user_input['sort_by'])) {
235 $keys = array_keys($exposed_sorts);
236 $user_input['sort_by'] = array_shift($keys);
237 $form_state->setUserInput($user_input);
240 if ($this->options['expose_sort_order']) {
241 $form['sort_order'] = [
243 '#options' => $sort_order,
244 '#title' => $this->t('Order', [], ['context' => 'Sort order']),
245 '#default_value' => $default_sort_order,
248 $form['submit']['#weight'] = 10;
251 if (!empty($this->options['reset_button'])) {
252 $form['actions']['reset'] = [
253 '#value' => $this->options['reset_button_label'],
258 // Get an array of exposed filters, keyed by identifier option.
259 $exposed_filters = [];
260 foreach ($this->view->filter as $id => $handler) {
261 if ($handler->canExpose() && $handler->isExposed() && !empty($handler->options['expose']['identifier'])) {
262 $exposed_filters[$handler->options['expose']['identifier']] = $id;
265 $all_exposed = array_merge($exposed_sorts, $exposed_filters);
267 // Set the access to FALSE if there is no exposed input.
268 if (!array_intersect_key($all_exposed, $this->view->getExposedInput())) {
269 $form['actions']['reset']['#access'] = FALSE;
273 $pager = $this->view->display_handler->getPlugin('pager');
275 $pager->exposedFormAlter($form, $form_state);
276 $form_state->set('pager_plugin', $pager);
283 public function exposedFormValidate(&$form, FormStateInterface $form_state) {
284 if ($pager_plugin = $form_state->get('pager_plugin')) {
285 $pager_plugin->exposedFormValidate($form, $form_state);
292 public function exposedFormSubmit(&$form, FormStateInterface $form_state, &$exclude) {
293 if (!$form_state->isValueEmpty('op') && $form_state->getValue('op') == $this->options['reset_button_label']) {
294 $this->resetForm($form, $form_state);
296 if ($pager_plugin = $form_state->get('pager_plugin')) {
297 $pager_plugin->exposedFormSubmit($form, $form_state, $exclude);
298 $exclude[] = 'pager_plugin';
303 * Resets all the states of the exposed form.
305 * This method is called when the "Reset" button is triggered. Clears
306 * user inputs, stored session, and the form state.
309 * An associative array containing the structure of the form.
310 * @param \Drupal\Core\Form\FormStateInterface $form_state
311 * The current state of the form.
313 public function resetForm(&$form, FormStateInterface $form_state) {
314 // _SESSION is not defined for users who are not logged in.
316 // If filters are not overridden, store the 'remember' settings on the
317 // default display. If they are, store them on this display. This way,
318 // multiple displays in the same view can share the same filters and
319 // remember settings.
320 $display_id = ($this->view->display_handler->isDefaulted('filters')) ? 'default' : $this->view->current_display;
322 if (isset($_SESSION['views'][$this->view->storage->id()][$display_id])) {
323 unset($_SESSION['views'][$this->view->storage->id()][$display_id]);
326 // Set the form to allow redirect.
327 if (empty($this->view->live_preview) && !\Drupal::request()->isXmlHttpRequest()) {
328 $form_state->disableRedirect(FALSE);
331 $form_state->setRebuild();
332 $this->view->exposed_data = [];
335 $form_state->setRedirect('<current>');
336 $form_state->setValues([]);
342 public function getCacheMaxAge() {
343 return Cache::PERMANENT;
349 public function getCacheContexts() {
351 if ($this->options['expose_sort_order']) {
352 // The sort order query arg is just important in case there is a exposed
354 $has_exposed_sort_handler = FALSE;
355 /** @var \Drupal\views\Plugin\views\sort\SortPluginBase $sort_handler */
356 foreach ($this->displayHandler->getHandlers('sort') as $sort_handler) {
357 if ($sort_handler->isExposed()) {
358 $has_exposed_sort_handler = TRUE;
362 if ($has_exposed_sort_handler) {
363 $contexts[] = 'url.query_args:sort_order';
367 // Merge in cache contexts for all exposed filters to prevent display of
369 foreach ($this->displayHandler->getHandlers('filter') as $filter_hander) {
370 if ($filter_hander->isExposed()) {
371 $contexts = Cache::mergeContexts($contexts, $filter_hander->getCacheContexts());
381 public function getCacheTags() {