better_exposed_filters_exposed_form_plugin.inc

  1. cis7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  2. cle7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  3. ecd7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  4. elmsmedia7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  5. harmony7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  6. icor7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  7. meedjum_blog7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
  8. mooc7 sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc

Provides an Better Exposed Filters exposed form plugin for View 3.x.

Classes

File

sites/all/modules/ulmus/better_exposed_filters/better_exposed_filters_exposed_form_plugin.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Provides an Better Exposed Filters exposed form plugin for View 3.x.
  5. */
  6. class better_exposed_filters_exposed_form_plugin extends views_plugin_exposed_form_basic {
  7. function summary_title() {
  8. return t('BEF Settings');
  9. }
  10. function option_definition() {
  11. $options = parent::option_definition();
  12. // Add Better Exposed Filters options to those saved by Views.
  13. $options['bef'] = array('default' => array());
  14. // Options for the input required setting.
  15. $options['input_required'] = array('default' => FALSE);
  16. $options['text_input_required'] = array('default' => t('Select any filter and click on Apply to see results'), 'translatable' => TRUE);
  17. $options['text_input_required_format'] = array('default' => NULL);
  18. return $options;
  19. }
  20. function options_form(&$form, &$form_state) {
  21. parent::options_form($form, $form_state);
  22. $bef_options = array();
  23. // Get current settings and default values for new filters.
  24. $existing = $this->_bef_get_settings();
  25. /*
  26. * Add general options for exposed form items.
  27. */
  28. $bef_options['general']['allow_secondary'] = array(
  29. '#type' => 'checkbox',
  30. '#title' => t('Enable secondary exposed form options'),
  31. '#default_value' => $existing['general']['allow_secondary'],
  32. '#description' => t('Allows you to specify some exposed form elements as being secondary options and places those elements in a collapsible fieldset. Use this option to place some exposed filters in an "Advanced Search" area of the form, for example.'),
  33. );
  34. $bef_options['general']['secondary_label'] = array(
  35. '#type' => 'textfield',
  36. '#default_value' => $existing['general']['secondary_label'],
  37. '#title' => t('Secondary options label'),
  38. '#description' => t(
  39. 'The name of the fieldset to hold secondary options. This cannot be left blank or there will be no way to show/hide these options.'
  40. ),
  41. '#states' => array(
  42. 'required' => array(
  43. ':input[name="allow_secondary"]' => array('checked' => TRUE),
  44. ),
  45. ),
  46. // Use CTool's #dependency as it adds some margin-left which looks nice.
  47. // Also, you can't change the required state via #dependency...
  48. '#dependency' => array('edit-exposed-form-options-bef-general-allow-secondary' => array(1)),
  49. );
  50. /*
  51. * Add options for exposed sorts.
  52. */
  53. $exposed = FALSE;
  54. foreach ($this->display->handler->get_handlers('sort') as $label => $sort) {
  55. if ($sort->options['exposed']) {
  56. $exposed = TRUE;
  57. break;
  58. }
  59. }
  60. if ($exposed) {
  61. $bef_options['sort']['bef_format'] = array(
  62. '#type' => 'select',
  63. '#title' => t('Display exposed sort options as'),
  64. '#default_value' => $existing['sort']['bef_format'],
  65. '#options' => array(
  66. 'default' => t('Default select list'),
  67. 'bef' => t('Radio Buttons'),
  68. 'bef_links' => t('Links'),
  69. 'bef_toggle_links' => t('Toggle Links'),
  70. ),
  71. '#description' => t('Select a format for the exposed sort options. Note: the "toggle links" option will only work correctly if "Combine sort order with sort by" is checked in the "Advanced Sort Options" section.'),
  72. );
  73. $bef_options['sort']['advanced'] = array(
  74. '#type' => 'fieldset',
  75. '#title' => t('Advanced sort options'),
  76. '#collapsible' => TRUE,
  77. '#collapsed' => TRUE,
  78. );
  79. $bef_options['sort']['advanced']['collapsible'] = array(
  80. '#type' => 'checkbox',
  81. '#title' => t('Make sort options collapsible'),
  82. '#default_value' => $existing['sort']['advanced']['collapsible'],
  83. '#description' => t(
  84. 'Puts the sort options in a collapsible fieldset'
  85. ),
  86. );
  87. $bef_options['sort']['advanced']['collapsible_label'] = array(
  88. '#type' => 'textfield',
  89. '#title' => t('Collapsible fieldset title'),
  90. '#default_value' => empty($existing['sort']['advanced']['collapsible_label']) ? t('Sort options') : $existing['sort']['advanced']['collapsible_label'],
  91. '#description' => t('This cannot be left blank or there will be no way to show/hide sort options.'),
  92. '#dependency' => array('edit-exposed-form-options-bef-sort-advanced-collapsible' => array(1)),
  93. );
  94. $bef_options['sort']['advanced']['combine'] = array(
  95. '#type' => 'checkbox',
  96. '#title' => t('Combine sort order with sort by'),
  97. '#default_value' => $existing['sort']['advanced']['combine'],
  98. '#description' => t('Combines the sort by options and order (ascending or decending) into a single list. Use this to display "Option1 Desc", "Option1 Asc", "Option2 Desc", "Option2 Asc" in a single form element.'),
  99. );
  100. $bef_options['sort']['advanced']['combine_rewrite'] = array(
  101. '#type' => 'textarea',
  102. '#title' => t('Rewrite the text displayed'),
  103. '#default_value' => $existing['sort']['advanced']['combine_rewrite'],
  104. '#description' => t('Use this field to rewrite the text displayed for combined sort options and sort order. Use the format of current_value|replacement_value, one replacement per line. For example: <pre>
  105. Post date Asc|Oldest first
  106. Post date Desc|Newest first
  107. Title Asc|A -> Z
  108. Title Desc|Z -> A</pre> Leave the replacement value blank to remove an option altogether.'),
  109. '#dependency' => array('edit-exposed-form-options-bef-sort-advanced-combine' => array(1)),
  110. );
  111. $bef_options['sort']['advanced']['reset'] = array(
  112. '#type' => 'checkbox',
  113. '#title' => t('Include a "Reset sort" option'),
  114. '#default_value' => $existing['sort']['advanced']['reset'],
  115. '#description' => t('Adds a "Reset sort" link; Views will use the default sort order.'),
  116. );
  117. $bef_options['sort']['advanced']['reset_label'] = array(
  118. '#type' => 'textfield',
  119. '#title' => t('"Reset sort" label'),
  120. '#default_value' => $existing['sort']['advanced']['reset_label'],
  121. '#description' => t('This cannot be left blank if the above option is checked'),
  122. '#dependency' => array('edit-exposed-form-options-bef-sort-advanced-reset' => array(1)),
  123. );
  124. $bef_options['sort']['advanced']['is_secondary'] = array(
  125. '#type' => 'checkbox',
  126. '#title' => t('This is a secondary option'),
  127. '#default_value' => $existing['sort']['advanced']['is_secondary'],
  128. '#states' => array(
  129. 'visible' => array(
  130. ':input[name="allow_secondary"]' => array('checked' => TRUE),
  131. ),
  132. ),
  133. '#description' => t('Places this element in the secondary options portion of the exposed form.'),
  134. );
  135. }
  136. /*
  137. * Add options for exposed pager.
  138. */
  139. if (isset($this->display->display_options['pager']) && !empty($this->display->display_options['pager']['options']['expose']['items_per_page'])) {
  140. $bef_options['pager']['bef_format'] = array(
  141. '#type' => 'select',
  142. '#title' => t('Display exposed pager options as'),
  143. '#default_value' => $existing['pager']['bef_format'],
  144. '#options' => array(
  145. 'default' => t('Default select list'),
  146. 'bef' => t('Radio Buttons'),
  147. 'bef_links' => t('Links'),
  148. ),
  149. '#description' => t('Select a format for the exposed pager options.'),
  150. );
  151. $bef_options['pager']['is_secondary'] = array(
  152. '#type' => 'checkbox',
  153. '#title' => t('This is a secondary option'),
  154. '#default_value' => $existing['pager']['is_secondary'],
  155. '#states' => array(
  156. 'visible' => array(
  157. ':input[name="allow_secondary"]' => array('checked' => TRUE),
  158. ),
  159. ),
  160. '#description' => t('Places this element in the secondary options portion of the exposed form.'),
  161. );
  162. }
  163. // Only add the description text once -- it was getting a little long to be
  164. // added to each filter.
  165. $bef_filter_intro = FALSE;
  166. $form['input_required'] = array(
  167. '#type' => 'checkbox',
  168. '#default_value' => $existing['general']['input_required'],
  169. '#title' => t('Require input before results are shown'),
  170. '#description' => t("Emulates the built in <em>Input Required</em> exposed filter handler")
  171. );
  172. $form['text_input_required'] = array(
  173. '#type' => 'container',
  174. '#tree' => FALSE,
  175. '#states' => array(
  176. // Hide this field when the input_required checkbox is disabled.
  177. 'invisible' => array(
  178. ':input[name="exposed_form_options[input_required]"]' => array('checked' => FALSE),
  179. ),
  180. ),
  181. );
  182. $form['text_input_required']['text_input_required'] = array(
  183. '#type' => 'text_format',
  184. '#title' => t('Text on demand'),
  185. '#description' => t('Text to display instead of results until the user selects and applies an exposed filter.'),
  186. '#default_value' => $this->options['text_input_required'],
  187. '#format' => isset($this->options['text_input_required_format']) ? $this->options['text_input_required_format'] : filter_default_format(),
  188. '#wysiwyg' => FALSE,
  189. );
  190. // Go through each filter and add BEF options.
  191. foreach ($this->display->handler->get_handlers('filter') as $label => $filter) {
  192. if (!$filter->options['exposed']) {
  193. continue;
  194. }
  195. // If we're adding BEF filter options, add an intro to explain what's
  196. // going on.
  197. if (!$bef_filter_intro) {
  198. $link = l(t('BEF settings documentation'), 'http://drupal.org/node/1701012');
  199. $bef_options['bef_intro'] = array(
  200. '#markup' => '<h3>'
  201. . t('Exposed Filter Settings')
  202. . '</h3><p>'
  203. . t('This section lets you select additional options for exposed filters. Some options are only available in certain situations. If you do not see the options you expect, please see the !link page for more details.',
  204. array('!link' => $link))
  205. . '</p>',
  206. );
  207. $bef_filter_intro = TRUE;
  208. }
  209. // These filter operators get our standard options: select, radio or
  210. // checkboxes, links, etc.
  211. $bef_standard = FALSE;
  212. // These filters get a single on/off checkbox option for boolean
  213. // operators.
  214. $bef_single = FALSE;
  215. // Used for taxonomy filters with heirarchy.
  216. $bef_nested = FALSE;
  217. // Used for date-based filters.
  218. $bef_datepicker = FALSE;
  219. // Used for numeric, non-date filters.
  220. $bef_slider = FALSE;
  221. // Check various filter types and determine what options are available.
  222. if ($filter instanceof views_handler_filter_string || $filter instanceof views_handler_filter_in_operator) {
  223. if (in_array($filter->operator, array('in', 'or', 'and'))) {
  224. $bef_standard = TRUE;
  225. }
  226. if (in_array($filter->operator, array('empty', 'not empty'))) {
  227. $bef_standard = TRUE;
  228. if (!$filter->options['expose']['multiple']) {
  229. $bef_single = TRUE;
  230. }
  231. }
  232. }
  233. if ($filter instanceof views_handler_filter_boolean_operator) {
  234. $bef_standard = TRUE;
  235. if (!$filter->options['expose']['multiple']) {
  236. $bef_single = TRUE;
  237. }
  238. }
  239. if ($filter instanceof views_handler_filter_term_node_tid) {
  240. // Autocomplete and dropdown taxonomy filter are both instances of
  241. // view_handler_filter_term_node_tid, but we can't show BEF options for
  242. // the autocomplete widget.
  243. if ('textfield' == $filter->options['type']) {
  244. $bef_standard = FALSE;
  245. }
  246. elseif (!empty($filter->options['hierarchy'])) {
  247. $bef_nested = TRUE;
  248. }
  249. }
  250. if ($filter instanceof views_handler_filter_date || !empty($filter->date_handler)) {
  251. $bef_datepicker = TRUE;
  252. }
  253. // The date filter handler extends the numeric filter handler so we have
  254. // to exclude it specifically.
  255. if ($filter instanceof views_handler_filter_numeric && !($filter instanceof views_handler_filter_date)) {
  256. $bef_slider = TRUE;
  257. }
  258. // Search API extends the more general views_handler_filter rather than
  259. // operator-specific classes such as views_handler_filter_in_operator.
  260. // Handle those options here.
  261. if ($filter instanceof SearchApiViewsHandlerFilterOptions) {
  262. $bef_standard = TRUE;
  263. }
  264. elseif ($filter instanceof SearchApiViewsHandlerFilterDate) {
  265. $bef_datepicker = TRUE;
  266. if ($filter->options['is_grouped']) {
  267. $bef_standard = TRUE;
  268. }
  269. }
  270. elseif ($filter instanceof SearchApiViewsHandlerFilterBoolean) {
  271. $bef_single = TRUE;
  272. }
  273. // All filters can use the default filter exposed by Views.
  274. $display_options = array('default' => t('Default select list'));
  275. if ($bef_standard) {
  276. // Main BEF option: radios/checkboxes.
  277. $display_options['bef'] = t('Checkboxes/Radio Buttons');
  278. }
  279. if ($bef_nested) {
  280. $display_options['bef_ul'] = t('Nested Checkboxes/Radio Buttons');
  281. }
  282. if ($bef_single) {
  283. $display_options['bef_single'] = t('Single on/off checkbox');
  284. }
  285. if ($bef_datepicker) {
  286. $display_options['bef_datepicker'] = t('jQuery UI Datepicker');
  287. }
  288. if ($bef_slider) {
  289. $display_options['bef_slider'] = t('jQuery UI slider');
  290. }
  291. if ($bef_standard) {
  292. // Less used BEF options, so put them last.
  293. $display_options['bef_links'] = t('Links');
  294. $display_options['bef_hidden'] = t('Hidden');
  295. }
  296. $identifier = '"' . $filter->options['expose']['identifier'] . '"';
  297. if (!empty($filter->options['expose']['label'])) {
  298. $identifier .= t(' (Filter label: "@fl")', array('@fl' => $filter->options['expose']['label']));
  299. }
  300. $bef_options[$label]['bef_format'] = array(
  301. '#type' => 'select',
  302. '#title' => t('Display @identifier exposed filter as', array('@identifier' => $identifier)),
  303. '#default_value' => $existing[$label]['bef_format'],
  304. '#options' => $display_options,
  305. );
  306. if ($bef_slider) {
  307. // Fieldset for jQuery slider options.
  308. $bef_options[$label]['slider_options'] = array(
  309. '#type' => 'fieldset',
  310. '#title' => t('Slider options for @identifier', array('@identifier' => $identifier)),
  311. '#collapsible' => TRUE,
  312. '#collapsed' => FALSE,
  313. '#states' => array(
  314. 'visible' => array(
  315. ':input[name="exposed_form_options[bef][' . $label . '][bef_format]"]' => array('value' => 'bef_slider'),
  316. ),
  317. ),
  318. );
  319. $bef_options[$label]['slider_options']['bef_slider_min'] = array(
  320. '#type' => 'textfield',
  321. '#title' => t('Range minimum'),
  322. '#default_value' => $existing[$label]['slider_options']['bef_slider_min'],
  323. '#bef_filter_id' => $label,
  324. '#states' => array(
  325. 'required' => array(
  326. ':input[name="exposed_form_options[bef][' . $label . '][bef_format]"]' => array('value' => 'bef_slider'),
  327. ),
  328. ),
  329. '#description' => t('The minimum allowed value for the jQuery range slider. It can be positive, negative, or zero and have up to 11 decimal places.'),
  330. '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_min_max'),
  331. );
  332. $bef_options[$label]['slider_options']['bef_slider_max'] = array(
  333. '#type' => 'textfield',
  334. '#title' => t('Range maximum'),
  335. '#default_value' => $existing[$label]['slider_options']['bef_slider_max'],
  336. '#bef_filter_id' => $label,
  337. '#states' => array(
  338. 'required' => array(
  339. ':input[name="exposed_form_options[bef][' . $label . '][bef_format]"]' => array('value' => 'bef_slider'),
  340. ),
  341. ),
  342. '#description' => t('The maximum allowed value for the jQuery range slider. It can be positive, negative, or zero and have up to 11 decimal places.'),
  343. '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_min_max'),
  344. );
  345. $bef_options[$label]['slider_options']['bef_slider_step'] = array(
  346. '#type' => 'textfield',
  347. '#title' => t('Step'),
  348. '#default_value' => empty($existing[$label]['slider_options']['bef_slider_step']) ? 1 : $existing[$label]['slider_options']['bef_slider_step'],
  349. '#bef_filter_id' => $label,
  350. '#states' => array(
  351. 'required' => array(
  352. ':input[name="exposed_form_options[bef][' . $label . '][bef_format]"]' => array('value' => 'bef_slider'),
  353. ),
  354. ),
  355. '#description' => t('Determines the size or amount of each interval or step the slider takes between the min and max.') . '<br />' .
  356. t('The full specified value range of the slider (Range maximum - Range minimum) must be evenly divisible by the step.') . '<br />' .
  357. t('The step must be a positive number of up to 5 decimal places.'),
  358. '#element_validate' => array('element_validate_number', 'better_exposed_filters_element_validate_slider_required', 'better_exposed_filters_element_validate_slider_step'),
  359. );
  360. $bef_options[$label]['slider_options']['bef_slider_animate'] = array(
  361. '#type' => 'textfield',
  362. '#title' => t('Animate'),
  363. '#default_value' => $existing[$label]['slider_options']['bef_slider_animate'],
  364. '#bef_filter_id' => $label,
  365. '#description' => t('Whether to slide handle smoothly when user click outside handle on the bar. Allowed values are "slow", "normal", "fast" or the number of milliseconds to run the animation (e.g. 1000). If left blank, there will be no animation, the slider will just jump to the new value instantly.'),
  366. '#element_validate' => array('better_exposed_filters_element_validate_slider_animate'),
  367. );
  368. $bef_options[$label]['slider_options']['bef_slider_orientation'] = array(
  369. '#type' => 'select',
  370. '#title' => t('Orientation'),
  371. '#options' => array(
  372. 'horizontal' => t('Horizontal'),
  373. 'vertical' => t('Vertical'),
  374. ),
  375. '#default_value' => $existing[$label]['slider_options']['bef_slider_orientation'],
  376. '#bef_filter_id' => $label,
  377. '#states' => array(
  378. 'required' => array(
  379. ':input[name="exposed_form_options[bef][' . $label . '][bef_format]"]' => array('value' => 'bef_slider'),
  380. ),
  381. ),
  382. '#description' => t('The orientation of the jQuery range slider.'),
  383. );
  384. }
  385. // Fieldset to keep the UI from getting out of hand.
  386. $bef_options[$label]['more_options'] = array(
  387. '#type' => 'fieldset',
  388. '#title' => t('More options for @identifier', array('@identifier' => $identifier)),
  389. '#collapsible' => TRUE,
  390. '#collapsed' => TRUE,
  391. );
  392. // Select all checkbox.
  393. if ($bef_standard) {
  394. $bef_options[$label]['more_options']['bef_select_all_none'] = array(
  395. '#type' => 'checkbox',
  396. '#title' => t('Add select all/none links'),
  397. '#default_value' => $existing[$label]['more_options']['bef_select_all_none'],
  398. '#disabled' => !$filter->options['expose']['multiple'],
  399. '#description' => t(
  400. 'Add a "Select All/None" link when rendering the exposed filter using
  401. checkboxes. If this option is disabled, edit the filter and check the
  402. "Allow multiple selections".'
  403. ),
  404. );
  405. if ($bef_nested) {
  406. $bef_options[$label]['more_options']['bef_select_all_none_nested'] = array(
  407. '#type' => 'checkbox',
  408. '#title' => t('Add nested all/none selection'),
  409. '#default_value' => $existing[$label]['more_options']['bef_select_all_none_nested'],
  410. '#disabled' => !$filter->options['expose']['multiple'] || !$filter->options['hierarchy'],
  411. '#description' => t(
  412. 'When a parent checkbox is checked, check all its children. If this option
  413. is disabled, edit the filter and check "Allow multiple selections" and
  414. edit the filter settings and check "Show hierarchy in dropdown".'
  415. ),
  416. );
  417. }
  418. // Put filter in collapsible fieldset option.
  419. // TODO: expand to all exposed filters.
  420. $bef_options[$label]['more_options']['bef_collapsible'] = array(
  421. '#type' => 'checkbox',
  422. '#title' => t('Make this filter collapsible'),
  423. '#default_value' => $existing[$label]['more_options']['bef_collapsible'],
  424. '#description' => t(
  425. 'Puts this filter in a collapsible fieldset'
  426. ),
  427. );
  428. }
  429. // Allow any filter to be moved into the secondary options fieldset.
  430. $bef_options[$label]['more_options']['is_secondary'] = array(
  431. '#type' => 'checkbox',
  432. '#title' => t('This is a secondary option'),
  433. '#default_value' => $existing[$label]['more_options']['is_secondary'],
  434. '#states' => array(
  435. 'visible' => array(
  436. ':input[name="allow_secondary"]' => array('checked' => TRUE),
  437. ),
  438. ),
  439. '#description' => t('Places this element in the secondary options portion of the exposed form.'),
  440. );
  441. // Allow "Any" label to be overridden.
  442. $bef_options[$label]['more_options']['any_label'] = array(
  443. '#type' => 'textfield',
  444. '#title' => t('Override "Any" option label'),
  445. '#default_value' => $existing[$label]['more_options']['any_label'],
  446. '#description' => t('Leave blank to use views default value. <em>Note: overriding this label will break translations and localizations. Leave this field blank if preserving internationalization is important to your site.</em>'),
  447. );
  448. // Build a description option form element -- available to all exposed
  449. // filters.
  450. $bef_options[$label]['more_options']['bef_filter_description'] = array(
  451. '#type' => 'textarea',
  452. '#title' => t('Description'),
  453. '#default_value' => $existing[$label]['more_options']['bef_filter_description'],
  454. '#description' => t('Adds descriptive text to the exposed filter. This is usually rendered in smaller print under the label or the options.'),
  455. );
  456. // Add token support to the description field.
  457. $bef_options[$label]['more_options']['tokens'] = array(
  458. '#title' => t('Replacement patterns'),
  459. '#type' => 'fieldset',
  460. '#collapsible' => TRUE,
  461. '#collapsed' => TRUE,
  462. );
  463. if (!module_exists('token')) {
  464. $bef_options[$label]['more_options']['tokens']['no_tokens'] = array(
  465. '#markup' => '<p>'
  466. . t('Enable the !token module to use replacement values.', array('!token' => l(t('Token'), 'http://drupal.org/project/token')))
  467. . '</p>',
  468. );
  469. }
  470. // Collect a list of token types that make sense for this filter.
  471. $available = array('global_types');
  472. if (!empty($filter->options['vid'])) {
  473. $available[] = 'vocabulary';
  474. }
  475. /* @TODO: Other token types? */
  476. $bef_options[$label]['more_options']['tokens']['list'] = array(
  477. '#theme' => 'token_tree',
  478. '#token_types' => $available,
  479. );
  480. $bef_options[$label]['more_options']['tokens']['available'] = array(
  481. // Save us from parsing available tokens again.
  482. '#type' => 'value',
  483. '#value' => $available,
  484. );
  485. // Allow rewriting of filter options for any filter.
  486. $bef_options[$label]['more_options']['rewrite'] = array(
  487. '#title' => t('Rewrite filter options'),
  488. '#type' => 'fieldset',
  489. '#collapsible' => TRUE,
  490. '#collapsed' => TRUE,
  491. );
  492. $bef_options[$label]['more_options']['rewrite']['filter_rewrite_values'] = array(
  493. '#type' => 'textarea',
  494. '#title' => t('Rewrite the text displayed'),
  495. '#default_value' => $existing[$label]['more_options']['rewrite']['filter_rewrite_values'],
  496. '#description' => t('
  497. Use this field to rewrite the filter options displayed. Use the format
  498. of current_value|replacement_value, one replacement per line. For
  499. example: <pre>
  500. 0|Zero
  501. 1|One
  502. 2|Two
  503. </pre> Leave the replacement value blank to remove an option altogether. If using hierarchical taxonomy filters, do not including leading hyphens in the current value.
  504. '),
  505. );
  506. }
  507. /* Ends: foreach ($filters as $filter) { */
  508. // Add BEF form elements to the exposed form options form.
  509. $form['bef'] = $bef_options;
  510. }
  511. /**
  512. * Tweak the exposed filter form to show Better Exposed Filter options.
  513. *
  514. * @param array $form
  515. * Exposed form array
  516. * @param array $form_state
  517. * Current state of form variables
  518. */
  519. function exposed_form_alter(&$form, &$form_state) {
  520. parent::exposed_form_alter($form, $form_state);
  521. global $language;
  522. // If we have no visible elements, we don't show the Apply button.
  523. $show_apply = FALSE;
  524. // Collect BEF's Javascript settings, add to Drupal.settings at the end.
  525. // Historical note: We used to only add BEF's Javascript when absolutely
  526. // needed. Eventually, much of that functionality worked its way into the
  527. // normal usage of BEF so that we now turn those Jvaascript behaviors on
  528. // by default. (See https://drupal.org/node/1807114).
  529. $bef_add_js = TRUE;
  530. $bef_js = array(
  531. 'datepicker' => FALSE,
  532. 'slider' => FALSE,
  533. 'settings' => array(),
  534. );
  535. // Some widgets will require additional CSS.
  536. $bef_add_css = FALSE;
  537. // Grab BEF settings.
  538. $settings = $this->_bef_get_settings();
  539. // Some elements may be placed in a secondary fieldset (eg: "Advanced
  540. // search options"). Place this after the exposed filters and before the
  541. // rest of the items in the exposed form.
  542. if ($allow_secondary = $settings['general']['allow_secondary']) {
  543. // If one of the secondary widgets has exposed input, do not collapse
  544. // the secondary fieldset.
  545. $secondary_collapse = TRUE;
  546. foreach ($this->view->get_exposed_input() as $key => $value) {
  547. if (!empty($value) && isset($settings[$key]['more_options']['is_secondary'])) {
  548. $secondary_collapse = FALSE;
  549. break;
  550. }
  551. }
  552. $secondary = array(
  553. '#type' => 'fieldset',
  554. '#title' => $settings['general']['secondary_label'],
  555. '#collapsible' => TRUE,
  556. '#collapsed' => $secondary_collapse,
  557. '#theme' => 'secondary_exposed_elements',
  558. );
  559. }
  560. /*
  561. * Handle exposed sort elements.
  562. */
  563. if (isset($settings['sort']) && !empty($form['sort_by']) && !empty($form['sort_order'])) {
  564. $show_apply = TRUE;
  565. // If selected, collect all sort-related form elements and put them
  566. // in a collapsible fieldset.
  567. $collapse = $settings['sort']['advanced']['collapsible']
  568. && !empty($settings['sort']['advanced']['collapsible_label']);
  569. $sort_elems = array();
  570. // Check for combined sort_by and sort_order.
  571. if ($settings['sort']['advanced']['combine']) {
  572. // Combine sort_by and sort_order into a single element.
  573. $form['sort_bef_combine'] = array(
  574. '#type' => 'radios',
  575. // Already sanitized by Views.
  576. '#title' => $form['sort_by']['#title'],
  577. );
  578. $options = array();
  579. // If using the bef_toggle_links format, determine which links should
  580. // not be shown.
  581. $hidden_options = array();
  582. // Add reset sort option at the top of the list.
  583. if ($settings['sort']['advanced']['reset']) {
  584. $options[' '] = t($settings['sort']['advanced']['reset_label']);
  585. }
  586. else {
  587. $form['sort_bef_combine']['#default_value'] = '';
  588. }
  589. $selected = '';
  590. $used_sort_keys = array();
  591. foreach ($form['sort_by']['#options'] as $by_key => $by_val) {
  592. foreach ($form['sort_order']['#options'] as $order_key => $order_val) {
  593. // Use a space to separate the two keys, we'll unpack them in our
  594. // submit handler.
  595. $options["$by_key $order_key"] = "$by_val $order_val";
  596. if ($form['sort_order']['#default_value'] == $order_key && empty($selected)) {
  597. // Respect default sort order set in Views. The default sort field
  598. // will be the first one if there are multiple sort criteria.
  599. $selected = "$by_key $order_key";
  600. }
  601. if ($settings['sort']['bef_format'] == 'bef_toggle_links') {
  602. if (isset($used_sort_keys[$by_key])
  603. || (!empty($form_state['input']['sort_bef_combine']) && $form_state['input']['sort_bef_combine'] == "$by_key $order_key")
  604. || (empty($form_state['input']['sort_bef_combine']) && $selected == "$by_key $order_key")
  605. ) {
  606. $hidden_options["$by_key $order_key"] = "$by_val $order_val";
  607. }
  608. else {
  609. $used_sort_keys[$by_key] = $order_key;
  610. }
  611. }
  612. }
  613. }
  614. $view = $form_state['view'];
  615. // Rewrite the option values if any were specified.
  616. if (!empty($settings['sort']['advanced']['combine_rewrite'])) {
  617. $combine_rewrite = trim($settings['sort']['advanced']['combine_rewrite']);
  618. $textgroup = implode(':', array(
  619. 'better_exposed_filters',
  620. 'combine_rewrite',
  621. $view->name,
  622. $view->current_display,
  623. ));
  624. $combine_rewrite = better_exposed_filters_translate($textgroup, $combine_rewrite);
  625. $lines = explode("\n", $combine_rewrite);
  626. $rewrite = array();
  627. foreach ($lines as $line) {
  628. list($search, $replace) = explode('|', $line);
  629. if (!empty($search)) {
  630. $rewrite[$search] = $replace;
  631. }
  632. }
  633. foreach ($options as $index => $option) {
  634. if (isset($rewrite[$option])) {
  635. if ('' == $rewrite[$option]) {
  636. unset($options[$index]);
  637. if ($selected == $index) {
  638. // Avoid "Illegal choice" errors.
  639. $selected = NULL;
  640. }
  641. }
  642. else {
  643. $options[$index] = $rewrite[$option];
  644. }
  645. }
  646. }
  647. }
  648. $form['sort_bef_combine'] = array(
  649. '#type' => 'radios',
  650. '#options' => $options,
  651. '#hidden_options' => $hidden_options,
  652. '#settings' => array(
  653. 'toggle_links' => ($settings['sort']['bef_format'] == 'bef_toggle_links'),
  654. ),
  655. '#default_value' => $selected,
  656. // Already sanitized by Views.
  657. '#title' => $form['sort_by']['#title'],
  658. );
  659. // Handle display-specific details.
  660. switch ($settings['sort']['bef_format']) {
  661. case 'bef':
  662. $form['sort_bef_combine']['#prefix'] = '<div class="bef-sort-combined bef-select-as-radios">';
  663. $form['sort_bef_combine']['#suffix'] = '</div>';
  664. break;
  665. case 'bef_links':
  666. case 'bef_toggle_links':
  667. $form['sort_bef_combine']['#theme'] = 'select_as_links';
  668. // Exposed form displayed as blocks can appear on pages other than
  669. // the view results appear on. This can cause problems with
  670. // select_as_links options as they will use the wrong path. We
  671. // provide a hint for theme functions to correct this.
  672. if (!empty($this->display->display_options['exposed_block'])) {
  673. $form['sort_bef_combine']['#bef_path'] = $this->display->display_options['path'];
  674. }
  675. break;
  676. case 'default':
  677. $form['sort_bef_combine']['#type'] = 'select';
  678. break;
  679. }
  680. // Add our submit routine to process.
  681. $form['#submit'][] = 'bef_sort_combine_submit';
  682. // Pretend we're another exposed form widget.
  683. $form['#info']['sort-sort_bef_combine'] = array(
  684. 'value' => 'sort_bef_combine',
  685. );
  686. // Remove the existing sort_by and sort_order elements.
  687. unset($form['sort_by']);
  688. unset($form['sort_order']);
  689. if ($collapse) {
  690. $sort_elems[] = 'sort_bef_combine';
  691. }
  692. }
  693. /* if ($settings['sort']['advanced']['combine']) { } */
  694. else {
  695. // Leave sort_by and sort_order as separate elements.
  696. if ('bef' == $settings['sort']['bef_format']) {
  697. $form['sort_by']['#type'] = 'radios';
  698. if (empty($form['sort_by']['#process'])) {
  699. $form['sort_by']['#process'] = array();
  700. }
  701. array_unshift($form['sort_by']['#process'], 'form_process_radios');
  702. $form['sort_by']['#prefix'] = '<div class="bef-sortby bef-select-as-radios">';
  703. $form['sort_by']['#suffix'] = '</div>';
  704. $form['sort_order']['#type'] = 'radios';
  705. if (empty($form['sort_order']['#process'])) {
  706. $form['sort_order']['#process'] = array();
  707. }
  708. array_unshift($form['sort_order']['#process'], 'form_process_radios');
  709. $form['sort_order']['#prefix'] = '<div class="bef-sortorder bef-select-as-radios">';
  710. $form['sort_order']['#suffix'] = '</div>';
  711. }
  712. elseif ('bef_links' == $settings['sort']['bef_format']) {
  713. $form['sort_by']['#theme'] = 'select_as_links';
  714. $form['sort_order']['#theme'] = 'select_as_links';
  715. // Exposed form displayed as blocks can appear on pages other than the
  716. // view results appear on. This can cause problems with
  717. // select_as_links options as they will use the wrong path. We provide
  718. // a hint for theme functions to correct this.
  719. if (!empty($this->display->display_options['exposed_block'])) {
  720. $form['sort_by']['#bef_path'] = $this->display->display_options['path'];
  721. $form['sort_order']['#bef_path'] = $this->display->display_options['path'];
  722. }
  723. }
  724. if ($collapse) {
  725. $sort_elems[] = 'sort_by';
  726. $sort_elems[] = 'sort_order';
  727. }
  728. // Add reset sort option if selected.
  729. if ($settings['sort']['advanced']['reset']) {
  730. array_unshift($form['sort_by']['#options'], $settings['sort']['advanced']['reset_label']);
  731. }
  732. }
  733. /* Ends: if ($settings['sort']['advanced']['combine']) { ... } else { */
  734. if ($collapse) {
  735. $form['bef_sort_options'] = array(
  736. '#type' => 'fieldset',
  737. '#collapsible' => TRUE,
  738. '#collapsed' => TRUE,
  739. '#title' => $settings['sort']['advanced']['collapsible_label'],
  740. );
  741. foreach ($sort_elems as $elem) {
  742. $form['bef_sort_options'][$elem] = $form[$elem];
  743. unset($form[$elem]);
  744. }
  745. }
  746. // Check if this is a secondary form element.
  747. if ($allow_secondary && $settings['sort']['advanced']['is_secondary']) {
  748. foreach (array('sort_bef_combine', 'sort_by', 'sort_order') as $elem) {
  749. if (!empty($form[$elem])) {
  750. $secondary[$elem] = $form[$elem];
  751. unset($form[$elem]);
  752. }
  753. }
  754. }
  755. }
  756. /* Ends: if (isset($settings['sort'])) { */
  757. /*
  758. * Handle exposed pager elements.
  759. */
  760. if (isset($settings['pager'])) {
  761. $show_apply = TRUE;
  762. switch ($settings['pager']['bef_format']) {
  763. case 'bef':
  764. $form['items_per_page']['#type'] = 'radios';
  765. if (empty($form['items_per_page']['#process'])) {
  766. $form['items_per_page']['#process'] = array();
  767. }
  768. array_unshift($form['items_per_page']['#process'], 'form_process_radios');
  769. $form['items_per_page']['#prefix'] = '<div class="bef-sortby bef-select-as-radios">';
  770. $form['items_per_page']['#suffix'] = '</div>';
  771. break;
  772. case 'bef_links':
  773. if (count($form['items_per_page']['#options']) > 1) {
  774. $form['items_per_page']['#theme'] = 'select_as_links';
  775. $form['items_per_page']['#items_per_page'] = max($form['items_per_page']['#default_value'], key($form['items_per_page']['#options']));
  776. // Exposed form displayed as blocks can appear on pages other than
  777. // the view results appear on. This can cause problems with
  778. // select_as_links options as they will use the wrong path. We
  779. // provide a hint for theme functions to correct this.
  780. if (!empty($this->display->display_options['exposed_block'])) {
  781. $form['items_per_page']['#bef_path'] = $this->display->display_options['path'];
  782. }
  783. }
  784. break;
  785. }
  786. // Check if this is a secondary form element.
  787. if ($allow_secondary && $settings['pager']['is_secondary']) {
  788. foreach (array('items_per_page', 'offset') as $elem) {
  789. if (!empty($form[$elem])) {
  790. $secondary[$elem] = $form[$elem];
  791. unset($form[$elem]);
  792. }
  793. }
  794. }
  795. }
  796. // Shorthand for all filters in this view.
  797. $filters = $form_state['view']->display_handler->handlers['filter'];
  798. // Go through each saved option looking for Better Exposed Filter settings.
  799. foreach ($settings as $label => $options) {
  800. // Sanity check: Ensure this filter is an exposed filter.
  801. if (empty($filters[$label]) || !$filters[$label]->options['exposed']) {
  802. continue;
  803. }
  804. // Form element is designated by the element ID which is user-
  805. // configurable.
  806. $field_id = $form['#info']["filter-$label"]['value'];
  807. // Token replacement on BEF Description fields.
  808. if (!empty($options['more_options']['bef_filter_description'])) {
  809. // Collect replacement data.
  810. $data = array();
  811. $available = $options['more_options']['tokens']['available'];
  812. if (in_array('vocabulary', $available)) {
  813. $vocabs = taxonomy_get_vocabularies();
  814. $data['vocabulary'] = $vocabs[$filters[$label]->options['vid']];
  815. }
  816. /* Others? */
  817. // Replace tokens.
  818. $options['more_options']['bef_filter_description'] = token_replace(
  819. $options['more_options']['bef_filter_description'], $data
  820. );
  821. $form[$field_id]['#bef_description'] = $options['more_options']['bef_filter_description'];
  822. }
  823. // Handle filter value rewrites.
  824. if (!empty($options['more_options']['rewrite']['filter_rewrite_values'])) {
  825. $lines = explode("\n", trim($options['more_options']['rewrite']['filter_rewrite_values']));
  826. $rewrite = array();
  827. foreach ($lines as $line) {
  828. list($search, $replace) = explode('|', $line);
  829. if (!empty($search)) {
  830. $rewrite[$search] = $replace;
  831. }
  832. }
  833. foreach ($form[$field_id]['#options'] as $index => $option) {
  834. $is_object = FALSE;
  835. if (is_object($option)) {
  836. // Taxonomy filters use objects instead of text.
  837. $is_object = TRUE;
  838. $option = reset($option->option);
  839. // Hierarchical filters prepend hyphens to indicate depth. We need
  840. // to remove them for comparison, but keep them after replacement to
  841. // ensure nested options display correctly.
  842. $option = ltrim($option, '-');
  843. }
  844. if (isset($rewrite[$option])) {
  845. if ('' == $rewrite[$option]) {
  846. unset($form[$field_id]['#options'][$index]);
  847. }
  848. else {
  849. if ($is_object) {
  850. // dsm($form[$field_id]['#options'][$index]->option, "$field_id at $index");
  851. // Taxonomy term filters are stored as objects. Use str_replace
  852. // to ensure that keep hyphens for hierarchical filters.
  853. list($tid, $original) = each($form[$field_id]['#options'][$index]->option);
  854. $form[$field_id]['#options'][$index]->option[$tid] = str_replace($option, $rewrite[$option], $original);
  855. }
  856. else {
  857. $form[$field_id]['#options'][$index] = $rewrite[$option];
  858. }
  859. }
  860. }
  861. }
  862. }
  863. // @TODO: Is this conditional needed anymore after the existing settings
  864. // array default values were added?
  865. if (!isset($options['bef_format'])) {
  866. $options['bef_format'] = '';
  867. }
  868. // These BEF options require a set of given options to work (namely,
  869. // $form[$field_id]['#options'] needs to set). But it is possilbe to
  870. // adjust settings elsewhere in the view that removes these options from
  871. // the form (eg: changing a taxonomy term filter from dropdown to
  872. // autocomplete). Check for that here and revert to Views' default filter
  873. // in those cases.
  874. $requires_options = array('bef', 'bef_ul', 'bef_links', 'bef_hidden');
  875. if (in_array($options['bef_format'], $requires_options) && !array_key_exists('#options', $form[$field_id])) {
  876. $options['bef_format'] = 'default';
  877. }
  878. switch ($options['bef_format']) {
  879. case 'bef_datepicker':
  880. $show_apply = TRUE;
  881. $bef_add_js = TRUE;
  882. $bef_js['datepicker'] = TRUE;
  883. $bef_js['datepicker_options'] = array();
  884. if ((
  885. // Single Date API-based input element.
  886. isset($form[$field_id]['value']['#type'])
  887. && 'date_text' == $form[$field_id]['value']['#type']
  888. )
  889. // Double Date-API-based input elements such as "in-between".
  890. || (isset($form[$field_id]['min']) && isset($form[$field_id]['max'])
  891. && 'date_text' == $form[$field_id]['min']['#type']
  892. && 'date_text' == $form[$field_id]['max']['#type']
  893. )) {
  894. /*
  895. * Convert Date API formatting to jQuery formatDate formatting.
  896. *
  897. * @TODO: To be honest, I'm not sure this is needed. Can you set a
  898. * Date API field to accept anything other than Y-m-d? Well, better
  899. * safe than sorry...
  900. *
  901. * @see http://us3.php.net/manual/en/function.date.php
  902. * @see http://docs.jquery.com/UI/Datepicker/formatDate
  903. *
  904. * Array format: PHP date format => jQuery formatDate format
  905. * (comments are for the PHP format, lines that are commented out do
  906. * not have a jQuery formatDate equivalent, but maybe someday they
  907. * will...)
  908. */
  909. $convert = array(
  910. /* Day */
  911. // Day of the month, 2 digits with leading zeros 01 to 31.
  912. 'd' => 'dd',
  913. // A textual representation of a day, three letters Mon through
  914. // Sun.
  915. 'D' => 'D',
  916. // Day of the month without leading zeros 1 to 31.
  917. 'j' => 'd',
  918. // (lowercase 'L') A full textual representation of the day of the
  919. // week Sunday through Saturday.
  920. 'l' => 'DD',
  921. // ISO-8601 numeric representation of the day of the week (added
  922. // in PHP 5.1.0) 1 (for Monday) through 7 (for Sunday).
  923. // 'N' => ' ',
  924. // English ordinal suffix for the day of the month, 2 characters
  925. // st, nd, rd or th. Works well with j.
  926. // 'S' => ' ',
  927. // Numeric representation of the day of the week 0 (for Sunday)
  928. // through 6 (for Saturday).
  929. // 'w' => ' ',
  930. // The day of the year (starting from 0) 0 through 365.
  931. 'z' => 'o',
  932. /* Week */
  933. // ISO-8601 week number of year, weeks starting on Monday (added
  934. // in PHP 4.1.0) Example: 42 (the 42nd week in the year).
  935. // 'W' => ' ',
  936. //
  937. /* Month */
  938. // A full textual representation of a month, such as January or
  939. // March January through December.
  940. 'F' => 'MM',
  941. // Numeric representation of a month, with leading zeros 01
  942. // through 12.
  943. 'm' => 'mm',
  944. // A short textual representation of a month, three letters Jan
  945. // through Dec.
  946. 'M' => 'M',
  947. // Numeric representation of a month, without leading zeros 1
  948. // through 12.
  949. 'n' => 'm',
  950. // Number of days in the given month 28 through 31.
  951. // 't' => ' ',
  952. //
  953. /* Year */
  954. // Whether it's a leap year 1 if it is a leap year, 0 otherwise.
  955. // 'L' => ' ',
  956. // ISO-8601 year number. This has the same value as Y, except that
  957. // if the ISO week number (W) belongs to the previous or next
  958. // year, that year is used instead. (added in PHP 5.1.0).
  959. // Examples: 1999 or 2003.
  960. // 'o' => ' ',
  961. // A full numeric representation of a year, 4 digits Examples:
  962. // 1999 or 2003.
  963. 'Y' => 'yy',
  964. // A two digit representation of a year Examples: 99 or 03.
  965. 'y' => 'y',
  966. /* Time */
  967. // Lowercase Ante meridiem and Post meridiem am or pm.
  968. // 'a' => ' ',
  969. // Uppercase Ante meridiem and Post meridiem AM or PM.
  970. // 'A' => ' ',
  971. // Swatch Internet time 000 through 999.
  972. // 'B' => ' ',
  973. // 12-hour format of an hour without leading zeros 1 through 12.
  974. // 'g' => ' ',
  975. // 24-hour format of an hour without leading zeros 0 through 23.
  976. // 'G' => ' ',
  977. // 12-hour format of an hour with leading zeros 01 through 12.
  978. // 'h' => ' ',
  979. // 24-hour format of an hour with leading zeros 00 through 23.
  980. // 'H' => ' ',
  981. // Minutes with leading zeros 00 to 59.
  982. // 'i' => ' ',
  983. // Seconds, with leading zeros 00 through 59.
  984. // 's' => ' ',
  985. // Microseconds (added in PHP 5.2.2) Example: 654321.
  986. // 'u' => ' ',
  987. );
  988. $format = '';
  989. if (isset($form[$field_id]['value'])) {
  990. $format = $form[$field_id]['value']['#date_format'];
  991. $form[$field_id]['value']['#attributes']['class'][] = 'bef-datepicker';
  992. }
  993. else {
  994. // Both min and max share the same format.
  995. $format = $form[$field_id]['min']['#date_format'];
  996. $form[$field_id]['min']['#attributes']['class'][] = 'bef-datepicker';
  997. $form[$field_id]['max']['#attributes']['class'][] = 'bef-datepicker';
  998. }
  999. $bef_js['datepicker_options']['dateformat'] = str_replace(array_keys($convert), array_values($convert), $format);
  1000. }
  1001. else {
  1002. $bef_js['datepicker_options']['dateformat'] = '';
  1003. /*
  1004. * Standard Drupal date field. Depending on the settings, the field
  1005. * can be at $form[$field_id] (single field) or
  1006. * $form[$field_id][subfield] for two-value date fields or filters
  1007. * with exposed operators.
  1008. */
  1009. $fields = array('min', 'max', 'value');
  1010. if (count(array_intersect($fields, array_keys($form[$field_id])))) {
  1011. foreach ($fields as $field) {
  1012. if (isset($form[$field_id][$field])) {
  1013. $form[$field_id][$field]['#attributes']['class'][] = 'bef-datepicker';
  1014. }
  1015. }
  1016. }
  1017. else {
  1018. $form[$field_id]['#attributes']['class'][] = 'bef-datepicker';
  1019. }
  1020. }
  1021. break;
  1022. case 'bef_slider':
  1023. $show_apply = TRUE;
  1024. $bef_add_js = TRUE;
  1025. $bef_add_css = TRUE;
  1026. $bef_js['slider'] = TRUE;
  1027. // Add js options for the slider for this filter.
  1028. $bef_js['slider_options'][$field_id] = array(
  1029. 'min' => $options['slider_options']['bef_slider_min'],
  1030. 'max' => $options['slider_options']['bef_slider_max'],
  1031. 'step' => $options['slider_options']['bef_slider_step'],
  1032. 'animate' => $options['slider_options']['bef_slider_animate'],
  1033. 'orientation' => $options['slider_options']['bef_slider_orientation'],
  1034. 'id' => drupal_html_id($field_id),
  1035. 'viewId' => $form['#id'],
  1036. );
  1037. break;
  1038. case 'bef_links':
  1039. $show_apply = TRUE;
  1040. $form[$field_id]['#theme'] = 'select_as_links';
  1041. // Exposed form displayed as blocks can appear on pages other than
  1042. // the view results appear on. This can cause problems with
  1043. // select_as_links options as they will use the wrong path. We provide
  1044. // a hint for theme functions to correct this.
  1045. if (!empty($this->display->display_options['exposed_block'])) {
  1046. $form[$field_id]['#bef_path'] = $this->display->display_options['path'];
  1047. }
  1048. break;
  1049. case 'bef_single':
  1050. $show_apply = TRUE;
  1051. // Use filter label as checkbox label.
  1052. $form[$field_id]['#title'] = $filters[$label]->options['expose']['label'];
  1053. $form[$field_id]['#description'] = $options['more_options']['bef_filter_description'];
  1054. $form[$field_id]['#return_value'] = 1;
  1055. $form[$field_id]['#type'] = 'checkbox';
  1056. // Handoff to the theme layer.
  1057. $form[$field_id]['#theme'] = 'checkbox';
  1058. break;
  1059. case 'bef_ul':
  1060. $show_apply = TRUE;
  1061. $form[$field_id]['#bef_nested'] = TRUE;
  1062. /* Intentionally falling through to case 'bef'. */
  1063. case 'bef':
  1064. $show_apply = TRUE;
  1065. if (empty($form[$field_id]['#multiple'])) {
  1066. // Single-select -- display as radio buttons.
  1067. $form[$field_id]['#type'] = 'radios';
  1068. if (empty($form[$field_id]['#process'])) {
  1069. $form[$field_id]['#process'] = array();
  1070. }
  1071. array_unshift($form[$field_id]['#process'], 'form_process_radios');
  1072. // Add description
  1073. if (!empty($form[$field_id]['#bef_description'])) {
  1074. $form[$field_id]['#description'] = $form[$field_id]['#bef_description'];
  1075. }
  1076. // Clean up objects from the options array (happens for taxonomy-
  1077. // based filters).
  1078. $opts = $form[$field_id]['#options'];
  1079. $form[$field_id]['#options'] = array();
  1080. foreach ($opts as $index => $opt) {
  1081. if (is_object($opt)) {
  1082. reset($opt->option);
  1083. list($key, $val) = each($opt->option);
  1084. $form[$field_id]['#options'][$key] = $val;
  1085. }
  1086. else {
  1087. $form[$field_id]['#options'][$index] = $opt;
  1088. }
  1089. }
  1090. if (isset($form[$field_id]['#options']['All'])) {
  1091. // @TODO: The terms 'All' and 'Any' are customizable in Views.
  1092. if ($filters[$label]->options['expose']['multiple']) {
  1093. // Some third-party filter handlers still add the "Any" option
  1094. // even if this is not an optional filter. Zap it here if they
  1095. // do.
  1096. unset($form[$field_id]['#options']['All']);
  1097. }
  1098. else {
  1099. // Otherwise, make sure the "Any" text is clean.
  1100. $form[$field_id]['#options']['All'] = check_plain($form[$field_id]['#options']['All']);
  1101. }
  1102. }
  1103. // Render as radio buttons or radio buttons in a collapsible
  1104. // fieldset.
  1105. if (!empty($options['more_options']['bef_collapsible'])) {
  1106. // Pass the description and title along in a way such that it
  1107. // doesn't get rendered as part of the exposed form widget. We'll
  1108. // render them as part of the fieldset.
  1109. if (isset($form['#info']["filter-$label"]['label'])) {
  1110. $form[$field_id]['#bef_title'] = $form['#info']["filter-$label"]['label'];
  1111. unset($form['#info']["filter-$label"]['label']);
  1112. }
  1113. if (!empty($options['more_options']['bef_filter_description'])) {
  1114. $form[$field_id]['#bef_description'] = $options['more_options']['bef_filter_description'];
  1115. if (isset($form[$field_id]['#description'])) {
  1116. unset($form[$field_id]['#description']);
  1117. }
  1118. }
  1119. // If the operator is exposed as well, put it inside the fieldset.
  1120. if ($filters[$label]->options['expose']['use_operator']) {
  1121. $operator_id = $filters[$label]->options['expose']['operator_id'];
  1122. $form[$field_id]['#bef_operator'] = $form[$operator_id];
  1123. unset ($form[$operator_id]);
  1124. }
  1125. // Add collapse/expand Javascript and BEF CSS to prevent collapsed
  1126. // fieldset from disappearing.
  1127. if (empty($form[$field_id]['#attached']['js'])) {
  1128. $form[$field_id]['#attached']['js'] = array();
  1129. }
  1130. $form[$field_id]['#attached']['js'][] = 'misc/form.js';
  1131. $form[$field_id]['#attached']['js'][] = 'misc/collapse.js';
  1132. if (empty($form[$field_id]['#attached']['css'])) {
  1133. $form[$field_id]['#attached']['css'] = array();
  1134. }
  1135. $form[$field_id]['#attached']['css'][] = drupal_get_path('module', 'better_exposed_filters') . '/better_exposed_filters.css';
  1136. // Take care of adding the fieldset in the theme layer.
  1137. $form[$field_id]['#theme'] = 'select_as_radios_fieldset';
  1138. }
  1139. /* if (!empty($options['more_options']['bef_collapsible'])) { */
  1140. else {
  1141. // Render select element as radio buttons.
  1142. $form[$field_id]['#attributes']['class'][] = 'bef-select-as-radios';
  1143. $form[$field_id]['#theme'] = 'select_as_radios';
  1144. }
  1145. }
  1146. /* if (empty($form[$field_id]['#multiple'])) { */
  1147. else {
  1148. // Render as checkboxes or checkboxes enclosed in a collapsible
  1149. // fieldset.
  1150. if (!empty($options['more_options']['bef_collapsible'])) {
  1151. // Pass the description and title along in a way such that it
  1152. // doesn't get rendered as part of the exposed form widget. We'll
  1153. // render them as part of the fieldset.
  1154. if (isset($form['#info']["filter-$label"]['label'])) {
  1155. $form[$field_id]['#bef_title'] = $form['#info']["filter-$label"]['label'];
  1156. unset($form['#info']["filter-$label"]['label']);
  1157. }
  1158. if (!empty($options['more_options']['bef_filter_description'])) {
  1159. $form[$field_id]['#bef_description'] = $options['more_options']['bef_filter_description'];
  1160. if (isset($form[$field_id]['#description'])) {
  1161. unset($form[$field_id]['#description']);
  1162. }
  1163. }
  1164. // If the operator is exposed as well, put it inside the fieldset.
  1165. if ($filters[$label]->options['expose']['use_operator']) {
  1166. $operator_id = $filters[$label]->options['expose']['operator_id'];
  1167. $form[$field_id]['#bef_operator'] = $form[$operator_id];
  1168. unset ($form[$operator_id]);
  1169. }
  1170. // Add collapse/expand Javascript and BEF CSS to prevent collapsed
  1171. // fieldset from disappearing.
  1172. if (empty($form[$field_id]['#attached']['js'])) {
  1173. $form[$field_id]['#attached']['js'] = array();
  1174. }
  1175. $form[$field_id]['#attached']['js'][] = 'misc/form.js';
  1176. $form[$field_id]['#attached']['js'][] = 'misc/collapse.js';
  1177. if (empty($form[$field_id]['#attached']['css'])) {
  1178. $form[$field_id]['#attached']['css'] = array();
  1179. }
  1180. $form[$field_id]['#attached']['css'][] = drupal_get_path('module', 'better_exposed_filters') . '/better_exposed_filters.css';
  1181. // Take care of adding the fieldset in the theme layer.
  1182. $form[$field_id]['#theme'] = 'select_as_checkboxes_fieldset';
  1183. }
  1184. else {
  1185. $form[$field_id]['#theme'] = 'select_as_checkboxes';
  1186. }
  1187. if ($options['more_options']['bef_select_all_none'] || $options['more_options']['bef_select_all_none_nested']) {
  1188. $bef_add_js = TRUE;
  1189. if ($options['more_options']['bef_select_all_none']) {
  1190. $form[$field_id]['#bef_select_all_none'] = TRUE;
  1191. }
  1192. if ($options['more_options']['bef_select_all_none_nested']) {
  1193. $form[$field_id]['#bef_select_all_none_nested'] = TRUE;
  1194. }
  1195. }
  1196. }
  1197. /* Ends: if (empty($form[$field_id]['#multiple'])) { ... } else { */
  1198. break;
  1199. case 'bef_hidden':
  1200. // Hide the label.
  1201. $form['#info']["filter-$label"]['label'] = '';
  1202. if (empty($form[$field_id]['#multiple'])) {
  1203. $form[$field_id]['#type'] = 'hidden';
  1204. }
  1205. else {
  1206. $form[$field_id]['#theme'] = 'select_as_hidden';
  1207. }
  1208. break;
  1209. default:
  1210. // Handle functionality for exposed filters that are not limited to
  1211. // BEF only filters.
  1212. $show_apply = TRUE;
  1213. // Add a description to the exposed filter.
  1214. if (!empty($options['more_options']['bef_filter_description'])) {
  1215. $form[$field_id]['#description'] = t($options['more_options']['bef_filter_description']);
  1216. }
  1217. break;
  1218. }
  1219. /* Ends switch ($options['bef_format']) */
  1220. // Check if this is a secondary form element.
  1221. if ($allow_secondary && $settings[$label]['more_options']['is_secondary']) {
  1222. $identifier = $form['#info']["filter-$label"]['value'];
  1223. if (!empty($form[$identifier])) {
  1224. // Move exposed operators with exposed filters
  1225. if (!empty($filters[$label]->options['expose']['use_operator'])) {
  1226. $op_id = $filters[$label]->options['expose']['operator_id'];
  1227. $secondary[$op_id] = $form[$op_id];
  1228. unset($form[$op_id]);
  1229. }
  1230. $secondary[$identifier] = $form[$identifier];
  1231. unset($form[$identifier]);
  1232. $secondary[$identifier]['#title'] = $form['#info']["filter-$label"]['label'];
  1233. unset($form['#info']["filter-$label"]);
  1234. }
  1235. }
  1236. }
  1237. // Override "Any" label, if applicable.
  1238. if (!empty($options['more_options']['any_label']) && !empty($form[$field_id]['#options']['All'])) {
  1239. $form[$field_id]['#options']['All'] = $options['more_options']['any_label'];
  1240. }
  1241. // If our form has no visible filters, hide the submit button.
  1242. $form['submit']['#access'] = $show_apply;
  1243. $form['reset']['#access'] = $show_apply;
  1244. // Add Javascript as needed.
  1245. if ($bef_add_js) {
  1246. // Add jQuery UI library code as needed.
  1247. if ($bef_js['datepicker']) {
  1248. drupal_add_library('system', 'ui.datepicker');
  1249. }
  1250. if ($bef_js['slider']) {
  1251. drupal_add_library('system', 'ui.slider');
  1252. }
  1253. drupal_add_js(array('better_exposed_filters' => $bef_js), 'setting');
  1254. drupal_add_js(drupal_get_path('module', 'better_exposed_filters') . '/better_exposed_filters.js');
  1255. }
  1256. if ($bef_add_css) {
  1257. drupal_add_css(drupal_get_path('module', 'better_exposed_filters') . '/better_exposed_filters.css');
  1258. }
  1259. // Check for secondary elements.
  1260. if ($allow_secondary && !empty($secondary)) {
  1261. // Add secondary elements after regular exposed filter elements.
  1262. $remaining = array_splice($form, count($form['#info']) + 1);
  1263. $form['secondary'] = $secondary;
  1264. $form = array_merge($form, $remaining);
  1265. $form['#info']['filter-secondary']['value'] = 'secondary';
  1266. }
  1267. }
  1268. /**
  1269. * Fills in missing settings with default values.
  1270. *
  1271. * Similar to array_merge_recursive, but later numeric keys overwrites earlier
  1272. * values. Use this to set defaults for missing values in a multi-dimensional
  1273. * array. Eg:
  1274. *
  1275. * $existing = $this->_bef_set_defaults($defaults, $existing);
  1276. *
  1277. * @return array
  1278. * The resulting settings array
  1279. */
  1280. function _bef_set_defaults() {
  1281. $count = func_num_args();
  1282. if (!$count) {
  1283. return;
  1284. }
  1285. elseif (1 == $count) {
  1286. return (func_get_arg(0));
  1287. }
  1288. // First array is the default values.
  1289. $params = func_get_args();
  1290. $return = array_shift($params);
  1291. // Merge the rest of the arrays onto the default array.
  1292. foreach ($params as $array) {
  1293. foreach ($array as $key => $value) {
  1294. // Numeric keyed values are added (unless already there).
  1295. if (is_numeric($key) && !in_array($value, $return)) {
  1296. if (is_array($value)) {
  1297. $return[] = $this->_bef_set_defaults($return[$key], $value);
  1298. }
  1299. else {
  1300. $return[] = $value;
  1301. }
  1302. }
  1303. // String keyed values are replaced.
  1304. else {
  1305. if (isset($return[$key]) && is_array($value) && is_array($return[$key])) {
  1306. $return[$key] = $this->_bef_set_defaults($return[$key], $value);
  1307. }
  1308. else {
  1309. $return[$key] = $value;
  1310. }
  1311. }
  1312. }
  1313. }
  1314. return $return;
  1315. }
  1316. /**
  1317. * Updates legacy settings to their current location.
  1318. *
  1319. * @param array $settings
  1320. * Array of BEF settings.
  1321. */
  1322. function _bef_update_legacy_settings($settings) {
  1323. // There has got to be a better way... But for now, this works.
  1324. if (isset($settings['sort']['collapsible'])) {
  1325. $settings['sort']['advanced']['collapsible'] = $settings['sort']['collapsible'];
  1326. unset($settings['sort']['collapsible']);
  1327. }
  1328. if (isset($settings['sort']['collapsible_label'])) {
  1329. $settings['sort']['advanced']['collapsible_label'] = $settings['sort']['collapsible_label'];
  1330. unset($settings['sort']['collapsible_label']);
  1331. }
  1332. if (isset($settings['sort']['combine'])) {
  1333. $settings['sort']['advanced']['combine'] = $settings['sort']['combine'];
  1334. unset($settings['sort']['combine']);
  1335. }
  1336. if (isset($settings['sort']['reset'])) {
  1337. $settings['sort']['advanced']['reset'] = $settings['sort']['reset'];
  1338. unset($settings['sort']['reset']);
  1339. }
  1340. if (isset($settings['sort']['reset_label'])) {
  1341. $settings['sort']['advanced']['reset_label'] = $settings['sort']['reset_label'];
  1342. unset($settings['sort']['reset_label']);
  1343. }
  1344. return $settings;
  1345. }
  1346. /**
  1347. * Returns an array of default or current existing values for BEF settings.
  1348. *
  1349. * This helps us as we add new options and prevents a lot of
  1350. * @code
  1351. * if (isset($settings['new_settings'])) { ... }
  1352. * @endcode
  1353. * as there will be a default value at all positions in the settings array.
  1354. * Also updates legacy settings to their new locations via
  1355. * _bef_update_legacy_settings().
  1356. *
  1357. * @return array
  1358. * Multi-dimensional settings array.
  1359. */
  1360. function _bef_get_settings() {
  1361. // General, sort, pagers, etc.
  1362. $defaults = array(
  1363. 'general' => array(
  1364. 'input_required' => FALSE,
  1365. 'allow_secondary' => FALSE,
  1366. 'secondary_label' => t('Advanced options'),
  1367. ),
  1368. 'sort' => array(
  1369. 'bef_format' => 'default',
  1370. 'advanced' => array(
  1371. 'collapsible' => FALSE,
  1372. 'collapsible_label' => '',
  1373. 'combine' => FALSE,
  1374. 'combine_rewrite' => '',
  1375. 'reset' => FALSE,
  1376. 'reset_label' => '',
  1377. 'is_secondary' => FALSE,
  1378. ),
  1379. ),
  1380. 'pager' => array(
  1381. 'bef_format' => 'default',
  1382. 'is_secondary' => FALSE,
  1383. ),
  1384. );
  1385. // Update legacy settings in the exposed form settings form. This
  1386. // keep us from losing settings when an option is put into an
  1387. // 'advanced options' fieldset.
  1388. $current = $this->_bef_update_legacy_settings($this->options['bef']);
  1389. // Collect existing values or use defaults.
  1390. $settings = $this->_bef_set_defaults($defaults, $current);
  1391. // Filter default values.
  1392. $filter_defaults = array(
  1393. 'bef_format' => 'default',
  1394. 'more_options' => array(
  1395. 'bef_select_all_none' => FALSE,
  1396. 'bef_select_all_none_nested' => FALSE,
  1397. 'bef_collapsible' => FALSE,
  1398. 'is_secondary' => FALSE,
  1399. 'bef_filter_description' => '',
  1400. 'any_label' => '',
  1401. 'tokens' => array(
  1402. 'list' => array(),
  1403. 'available' => array(),
  1404. ),
  1405. 'rewrite' => array(
  1406. 'filter_rewrite_values' => '',
  1407. ),
  1408. ),
  1409. 'slider_options' => array(
  1410. 'bef_slider_min' => 0,
  1411. 'bef_slider_max' => 99999,
  1412. 'bef_slider_step' => 1,
  1413. 'bef_slider_animate' => '',
  1414. 'bef_slider_orientation' => 'horizontal',
  1415. ),
  1416. );
  1417. // Go through each exposed filter and collect settings.
  1418. foreach ($this->display->handler->get_handlers('filter') as $label => $filter) {
  1419. if (!$filter->options['exposed']) {
  1420. continue;
  1421. }
  1422. // Get existing values or use defaults.
  1423. if (!isset($this->options['bef'][$label])) {
  1424. // First time opening the settings form with a new filter.
  1425. $settings[$label] = $filter_defaults;
  1426. }
  1427. else {
  1428. $settings[$label] = $this->_bef_set_defaults($filter_defaults, $this->options['bef'][$label]);
  1429. }
  1430. }
  1431. return $settings;
  1432. }
  1433. /**
  1434. * Util function to determine if any filters have been applied.
  1435. * Borrowed from views_plugin_exposed_form_input_required
  1436. */
  1437. function exposed_filter_applied() {
  1438. static $cache = NULL;
  1439. if (!isset($cache)) {
  1440. $view = $this->view;
  1441. if (is_array($view->filter) && count($view->filter)) {
  1442. foreach ($view->filter as $filter_id => $filter) {
  1443. if ($filter->is_exposed()) {
  1444. $identifier = $filter->options['expose']['identifier'];
  1445. if (isset($view->exposed_input[$identifier])) {
  1446. if (!empty($view->exposed_input[$identifier])) {
  1447. $cache = TRUE;
  1448. return $cache;
  1449. }
  1450. }
  1451. }
  1452. }
  1453. }
  1454. $cache = FALSE;
  1455. }
  1456. return $cache;
  1457. }
  1458. /**
  1459. * Pre render callback to append the 'no values found' text if input required
  1460. * options is enabled.
  1461. */
  1462. function pre_render($values) {
  1463. if (!$this->exposed_filter_applied() && !empty($this->options['input_required'])) {
  1464. $options = array(
  1465. 'id' => 'area',
  1466. 'table' => 'views',
  1467. 'field' => 'area',
  1468. 'label' => '',
  1469. 'relationship' => 'none',
  1470. 'group_type' => 'group',
  1471. 'content' => $this->options['text_input_required'],
  1472. 'format' => $this->options['text_input_required_format'],
  1473. 'empty' => TRUE,
  1474. );
  1475. $handler = views_get_handler('views', 'area', 'area');
  1476. $handler->init($this->view, $options);
  1477. $this->display->handler->handlers['empty'] = array(
  1478. 'area' => $handler,
  1479. );
  1480. $this->display->handler->set_option('empty', array('text' => $options));
  1481. }
  1482. }
  1483. /**
  1484. * Query callback, intervenes if no filters are applied and input is required.
  1485. */
  1486. function query() {
  1487. if (!$this->exposed_filter_applied() && !empty($this->options['input_required'])) {
  1488. // We return with no query; this will force the empty text.
  1489. $this->view->built = TRUE;
  1490. $this->view->executed = TRUE;
  1491. $this->view->result = array();
  1492. }
  1493. else {
  1494. parent::query();
  1495. }
  1496. }
  1497. /**
  1498. * Submit handler for the options to unpack the format/text from the
  1499. * text_format field.
  1500. */
  1501. function options_submit(&$form, &$form_state) {
  1502. $view = $form_state['view'];
  1503. $values = &$form_state['values'];
  1504. $values['exposed_form_options']['text_input_required_format'] = $values['text_input_required']['format'];
  1505. $values['exposed_form_options']['text_input_required'] = $values['text_input_required']['value'];
  1506. if (function_exists('i18n_string_update')) {
  1507. $combine_rewrite = $values['exposed_form_options']['bef']['sort']['advanced']['combine_rewrite'];
  1508. $textgroup = implode(':', array(
  1509. 'better_exposed_filters',
  1510. 'combine_rewrite',
  1511. $view->name,
  1512. $view->current_display,
  1513. ));
  1514. i18n_string_update($textgroup, $combine_rewrite, array(
  1515. 'format' => FALSE,
  1516. ));
  1517. }
  1518. parent::options_submit($form, $form_state);
  1519. }
  1520. }