better_exposed_filters.module 17.2 KB
Newer Older
1
2
3
4
5
<?php

/**
 * @file
 * Allows the use of checkboxes, radio buttons or hidden fields for exposed Views filters.
6
 *
7
 * Thanks to Ben Buckman (http://echodittolabs.org/) for the original concept.
8
9
 */

10
11
12
13
14
15
16
17
18
19
20
21
22

/**
 * Implements hook_form_alter()
 */
function better_exposed_filters_form_alter(&$form, $form_state, $form_id) {

  /*
   * Add new display options to the views config form
   *
   * NOTE: In Views 2.x these options were saved as part of the View and we use hook_form_alter
   *    to inject them into the filter config form.  In Views 3.x, with the addition of exposed
   *    forms, we can now do all of this in a plugin (better_exposed_filters_exposed_form_plugin.inc).
   *
23
24
25
26
   *    To keep things straight, I've created several branches: (currently) two 6.x branches,
   *    one to track Views 2.x (this branch) and another to track Views 3.x.
   *
   *    However, now there is duplicate code in several locations.  Any changes to hook_form_alter
27
   *    will need to be replicated in better_exposed_filters_exposed_form_plugin (either in
28
   *    options_form for options settings or exposed_form_alter for displaying the filter).
29
30
   */
  if ('2' != substr(views_api_version(), 0, 1)) {
31
    // Sanity check: only continue for Views 2.x
32
33
34
35
36
    return;
  }

  if ('views_ui_config_item_form' == $form_id && !empty($form['options']['expose'])) {

37
    // Collect current BEF values or set default values
38
    $curr = array();
39
40
41

    // Build additional form elements to inject into the exposed filter configuration form
    $left = $right = array();
42

43
    // Description is now allowed for any exposed filter
44
45
46
47
48
49
50
51
52
    $curr['description'] = empty($form_state['handler']->options['expose']['bef_filter_description']) ?
      '' : $form_state['handler']->options['expose']['bef_filter_description'];

    // Build a description option form element
    $left['bef_filter_description'] = array(
      '#type' => 'textarea',
      '#title' => t('Description'),
      '#default_value' => $curr['description'],
    );
53

54
55
56
57
58
59
60
61
62
63
64
    // Is this a field we can fully override?  If so, add additional BEF configuration options
    $overrideable = array('select', 'checkboxes', 'radios');
    if (in_array($form['options']['value']['#type'], $overrideable)) {

      // Collect the remaining existing/default values for BEF-operable exposed filters
      $curr['format'] = empty($form_state['handler']->options['expose']['bef_format']) ?
        'default' : $form_state['handler']->options['expose']['bef_format'];
      $curr['allnone'] = empty($form_state['handler']->options['expose']['bef_select_all_none']) ?
        FALSE : $form_state['handler']->options['expose']['bef_select_all_none'];
      $curr['collapsible'] = empty($form_state['handler']->options['expose']['bef_collapsible']) ?
        FALSE : $form_state['handler']->options['expose']['bef_collapsible'];
65
66
      $curr['termcount'] = empty($form_state['handler']->options['expose']['bef_show_term_count']) ?
        FALSE : $form_state['handler']->options['expose']['bef_show_term_count'];
67
68
      $curr['termdepth'] = empty($form_state['handler']->options['expose']['bef_taxonomy_depth']) ?
        0 : $form_state['handler']->options['expose']['bef_taxonomy_depth'];
69

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
      // Build format selection element
      $left['bef_format'] = array(
        '#type' => 'select',
        '#title' => t('Display exposed filter as'),
        '#default_value' => $curr['format'],
        '#options' => array(
          'default' => t('Default select list'),
          'bef' => t('Checkboxes/Radio Buttons'),
          'bef_ul' => t('Nested Checkboxes/Radio Buttons'),
          'bef_hidden' => t('Hidden'),
        ),
        '#description' => t(
          'Select a format for the exposed filter.  The "Nested" option places
           hierarchical taxonomy filters in a nested, unordered list.
           The "Hidden" option is
           generally used for multi-step filters.  Note: if "Force single"
           is checked, radio buttons will be used.  If "Force single" is
           unchecked, checkboxes will be used.'
        ),
      );
90

91
92
93
94
95
96
97
      if ($form['options']['value']['#type'] == 'radios'
          && count($form['options']['value']['#options']) == 3
          && isset($form['options']['value']['#options'][1])
          && isset($form['options']['value']['#options'][0])) {
        $left['bef_format']['#options']['bef_single'] = t('Single on/off checkbox');
      }

98
99
100
101
102
103
104
105
106
107
108
      // Build check all/none option form element
      $right['bef_select_all_none'] = array(
        '#type' => 'checkbox',
        '#title' => t('Add select all/none links'),
        '#default_value' => $curr['allnone'],
        '#description' => t(
          'Add a "Select All/None" link when rendering the exposed filter using
           checkboxes. NOTE: The link is built at page load, so it will not appear
           in the "Live Preview" which is loaded dynamically.'
        ),
      );
109

110
111
112
113
114
115
116
117
118
      // Put filter in collapsible fieldset option
      $right['bef_collapsible'] = array(
        '#type' => 'checkbox',
        '#title' => t('Make this filter collapsible'),
        '#default_value' => $curr['collapsible'],
        '#description' => t(
          'Puts this filter in a collapsible fieldset'
        ),
      );
119
120
121
122
123
124
125
126
127
128
129
130

      // Build show term count form element
      if (t('Taxonomy') == $form_state['handler']->definition['group']) {
        $right['bef_show_term_count'] = array(
          '#type' => 'checkbox',
          '#title' => t('Show term node count'),
          '#default_value' => $curr['termcount'],
          '#description' => t(
            'Show number of nodes after each term, in paranthesis, like this:
            "Term A (4), Term B (10)", etc.'
          ),
        );
131

132
133
134
135
136
137
138
139
140
141
142
143
144
        // Add option to limit depth of hierarchical taxonomy vocabs
        if ('views_handler_filter_term_node_tid_depth' == $form_state['handler']->definition['handler']) {
          $options = array();
          $options[0] = t('No limit');
          for ($i = 1; $i <= 10; $i++) {
            $options[$i] = format_plural($i, '1 level deep', '@count levels deep');
          }
          $right['bef_taxonomy_depth'] = array(
            '#type' => 'select',
            '#title' => t('Limit taxonomy w/ depth filter to'),
            '#options' => $options,
            '#default_value' => $curr['termdepth'],
            '#description' => t(
145
              'Limit the depth of a hierarchical taxonomy filter to this depth. For example, if your taxonomy includes Country -> State -> City, setting a limit of 2 would show only Country and State in the filter.'
146
147
            ),
          );
148
        }
149
      }                 // if (t('Taxonomy') == $form_state['handler']->definition['group']) {
150
151
   }                    // if (in_array($form['options']['value']['#type'], $overrideable)) {

152
    // Insert BEF form elements into the exposed filter form
153
154
155
156
157
158
159
160
161
162
    if (!empty($left)) {
      $expose = $form['options']['expose'];
      $first_chunk = array_splice($expose, 0, array_search('end_left', array_keys($expose)));
      $form['options']['expose'] = array_merge($first_chunk, $left, $expose);
    }
    if (!empty($right)) {
      $expose = $form['options']['expose'];
      $first_chunk = array_splice($expose, 0, array_search('end_checkboxes', array_keys($expose)));
      $form['options']['expose'] = array_merge($first_chunk, $right, $expose);
    }
163
164
165
166
167
168
169

  }     // if ('views_ui_config_item_form' ...) {

  /*
   * Update exposed filters to show either checkboxes or radio buttons
   */
  if ('views_exposed_form' == $form_id) {
170
171
172
    // If we have no visible filters, we don't show the Apply button
    $show_apply = FALSE;

173
174
175
176
177
178
179
    // Go through each filter checking for a Better Exposed Filter setting
    foreach ($form_state['view']->filter as $field => $filter) {
      if ($filter->options['exposed']) {

        // Form element is designated by the element ID which is user-configurable
        $field_id = $form['#info']["filter-$field"]['value'];

180
181
182
183
184
        // Show number of nodes after each term
        if ($filter->options['expose']['bef_show_term_count']) {
          $form[$field_id]['#termcount'] = $filter->options['expose']['bef_show_term_count'];
        }

185
        // Limit the depth of hierarchical taxonomy term filters
186
        if (!empty($filter->options['expose']['bef_taxonomy_depth'])) {
187
          $limit = $filter->options['expose']['bef_taxonomy_depth'];
188
189

          // Because top level terms have no '-' prepended to them
190
          $limit--;
191

192
193
194
195
196
          $remaining = array();
          foreach ($form[$field_id]['#options'] as $index => $option) {
            if (!is_object($option)) {
              // Most likely the "Any" option
              $remaining[] = $option;
197
              continue;
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
            }
            $option_text = $option->option;
            if (!$option_text) {
              // ????
              continue;
            }
            $option_text = array_pop($option_text);
            $true_option = ltrim($option_text[0], '-');
            if (!empty($true_option)) {
              $depth = strpos($option_text, $true_option);
              if ($depth === FALSE || $depth <= $limit) {
                $remaining[] = $option;
              }
            }
          }
          $form[$field_id]['#options'] = $remaining;
        }

216
217
218
219
220
221
222
223
        // Add a description to the exposed filter
        if (isset($filter->options['expose']['bef_filter_description'])) {
          $form[$field_id]['#description'] = $filter->options['expose']['bef_filter_description'];
        }

        if (isset($filter->options['expose']['bef_format'])) {
          switch ($filter->options['expose']['bef_format']) {
            case 'bef_ul':
224
              $show_apply = TRUE;
225
226
              $form[$field_id]['#bef_nested'] = TRUE;
              // Intentionally falling through
227

228
            case 'bef':
229
            case 'bef_single':
230
              $show_apply = TRUE;
231
232
233
234
              if (empty($form[$field_id]['#multiple'])) {
                // Single-select -- display as radio buttons
                $form[$field_id]['#type'] = 'radios';
                $form[$field_id]['#process'] = array('expand_radios', 'views_process_dependency');
235

236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
                // Use the single checkbox
                if ($filter->options['expose']['bef_format'] == 'bef_single') {
                  $form[$field_id]['#type'] = 'checkbox';
                  $form[$field_id]['#title'] = $form['#info']["filter-$field_id"]['label'];

                  // Remove this option. We only need the values 1 and 0
                  unset($form[$field_id]['#options']['All']);

                  // Drupal core has a bug dealing with type checkbox. This is a
                  // fix to make sure the checkboxes are on and off based on user
                  // selection
                  if ($form_state['input'][$field_id] == 'All') {
                    $form[$field_id]['#default_value'] = 0;
                    $form_state['input'][$field_id] = 0;
                  }
                  $form[$field_id]['#value'] = $form_state['input'][$field_id];
                }
253
254
255
256
257
                // Clean up objects from the options array (happens for taxonomy-based filters)
                $options = $form[$field_id]['#options'];
                $form[$field_id]['#options'] = array();
                foreach ($options as $index => $option) {
                  if (is_object($option)) {
258
259
260
                    foreach ($option->option as $key => $val) {
                      $form[$field_id]['#options'][$key] = $val;
                    }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
                  }
                  else {
                    $form[$field_id]['#options'][$index] = $option;
                  }
                }

                if (isset($form[$field_id]['#options']['All'])) {
                  // @TODO: The terms 'All' and 'Any' are customizable in Views
                  if (!$filter->options['expose']['optional']) {
                    // Some third-party filter handlers still add the "Any" option even if this is not
                    // an optional filter.  Zap it here if they do.
                    unset($form[$field_id]['#options']['All']);
                  }
                  else {
                    // Otherwise, make sure the "Any" text is clean
                    $form[$field_id]['#options']['All'] = check_plain($form[$field_id]['#options']['All']);
                  }
                }
279

280
281
282
                // Render as radio buttons or radio buttons in a collapsible fieldset
                if (!empty($filter->options['expose']['bef_collapsible'])) {
                  // Use the option label for the title of the fieldset
283
284
                  $form[$field_id]['#title'] = $form['#info']["filter-$field"]['label'];
                  unset($form['#info']["filter-$field"]['label']);
285

286
287
288
289
290
291
                  // Pass the description and title along in a way such that it doesn't get rendered as part of
                  // the exposed form widget.  We'll render them as part of the fieldset.
                  $form[$field_id]['#bef_description'] = $form[$field_id]['#description'];
                  unset($form[$field_id]['#description']);
                  $form[$field_id]['#bef_title'] = $form[$field_id]['#title'];
                  unset($form[$field_id]['#title']);
292

293
294
295
296
297
298
299
300
301
302
303
304
                  // Take care of adding the fieldset in the theme layer
                  $form[$field_id]['#theme'] = 'select_as_radios_fieldset';
                }
                else {
                  // Render select element as radio buttons
                  $form[$field_id]['#theme'] = 'select_as_radios';
                }
              }         // if (empty($form[$field_id]['#multiple'])) {
              else {
                // Render as checkboxes or checkboxes enclosed in a collapsible fieldset
                if (!empty($filter->options['expose']['bef_collapsible'])) {
                  // Use exposed filter widget label as legend for this fieldset
305
306
                  $form[$field_id]['#title'] = $form['#info']["filter-$field"]['label'];
                  unset($form['#info']["filter-$field"]['label']);
307

308
309
310
311
312
313
                  $form[$field_id]['#theme'] = 'select_as_checkboxes_fieldset';
                }
                else {
                  $form[$field_id]['#theme'] = 'select_as_checkboxes';
                }

314
315
                // Add BEF's JavaScript to the mix to handle select all/none functionality
                drupal_add_js(drupal_get_path('module', 'better_exposed_filters') .'/better_exposed_filters.js');
316

317
318
                // Add select all/none functionality to this filter.
                if ($filter->options['expose']['bef_select_all_none']) {
319
320
321
                  if (!isset($form[$field_id]['#attributes'])) {
                    $form[$field_id]['#attributes'] = array();
                  }
322
323
324
325
326
327
                  if (empty($form[$field_id]['#attributes']['class'])) {
                    $form[$field_id]['#attributes']['class'] = 'bef-select-all-none';
                  }
                  else {
                    $form[$field_id]['#attributes']['class'] .= ' bef-select-all-none';
                  }
328
329
330
331
332
                }
              }         // if (empty($form[$field_id]['#multiple'])) { ... } else {
              break;

            case 'bef_hidden':
333
              $form['#info']["filter-$field"]['label'] = '';     // Hide the label
334
335
336
337
338
339
340
              if (empty($form[$field_id]['#multiple'])) {
                $form[$field_id]['#type'] = 'hidden';
              }
              else {
                $form[$field_id]['#theme'] = 'select_as_hidden';
              }
              break;
341

342
343
344
            case 'default':
              $show_apply = TRUE;
              break;
345

346
          } // switch ($filter->options['expose']['bef_format']) {
347
        }   // if (isset($filter->options['expose']['bef_format'])) {
348
349
350
351
        else {
          // This is an exposed filter that is not controled by BEF.
          $show_apply = TRUE;
        }
352
353
      }     // if ($filter->options['exposed']) {
    }       // foreach (...)
354
355
356
357

    // If our form has no visible filters, hide the submit button.
    $form['submit']['#access'] = $show_apply;

358
359
360
  }         // if ('views_exposed_form' == $form_id)
}

361
/**
362
 * Implements hook_theme()
363
364
365
 */
function better_exposed_filters_theme($existing, $type, $theme, $path) {
  return array(
366
367
368
369
370
371
372
373
374
375
376
377
378
379
    'select_as_checkboxes'          => array('function' => 'theme_select_as_checkboxes',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_checkboxes_fieldset' => array('function' => 'theme_select_as_checkboxes_fieldset',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_radios'              => array('function' => 'theme_select_as_radios',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_radios_fieldset'     => array('function' => 'theme_select_as_radios_fieldset',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_hidden'              => array('function' => 'theme_select_as_hidden',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_tree'                => array('function' => 'theme_select_as_tree',
                                             'file' => 'better_exposed_filters.theme'),
    'select_as_links'               => array('function' => 'theme_select_as_links',
                                             'file' => 'better_exposed_filters.theme'),
380
381
  );
}