views_dependent_filters_handler_filter_dependent.inc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. /**
  3. *
  4. */
  5. class views_dependent_filters_handler_filter_dependent extends views_handler_filter {
  6. /**
  7. * The list of filters subsequent to ourselves that we should remove.
  8. * An array of keys of $this->view->filter.
  9. */
  10. var $filters_kill = array();
  11. /**
  12. * Disable the filters we control.
  13. */
  14. function filters_disable() {
  15. // This hacks subsequent handlers' options so they are no longer exposed.
  16. // Incoming values from these on submit will be simply ignored.
  17. foreach ($this->filters_kill as $filter_id) {
  18. $this->view->filter[$filter_id]->options['exposed'] = FALSE;
  19. }
  20. }
  21. /**
  22. * Enable the filters we control.
  23. */
  24. function filters_enable() {
  25. foreach ($this->filters_kill as $filter_id) {
  26. $this->view->filter[$filter_id]->options['exposed'] = TRUE;
  27. }
  28. }
  29. function option_definition() {
  30. $options = parent::option_definition();
  31. // Override the exposed default. This makes no sense not exposed.
  32. $options['exposed'] = array('default' => TRUE);
  33. $options['controller_filter'] = array('default' => NULL);
  34. $options['controller_values'] = array('default' => NULL);
  35. $options['dependent_filters'] = array('default' => array());
  36. return $options;
  37. }
  38. /**
  39. * Helper function to provide form options for lists of filters.
  40. *
  41. * @param $type
  42. * One of 'controller' or 'dependent'.
  43. *
  44. * @return
  45. * An array of filters suitable for use as Form API options.
  46. */
  47. function get_filter_options($type) {
  48. // Due to http://drupal.org/node/1426094 we can't just go looking in the
  49. // handlers array on the display.
  50. $filters = $this->view->display_handler->get_handlers('filter');
  51. // Get the unique id of this handler (ie allow more than one of this handler).
  52. $this_id = $this->options['id'];
  53. $filters_controller = array();
  54. $filters_dependent = array();
  55. $seen = FALSE;
  56. // Build up the options from all the fields up to this one but no further.
  57. foreach ($filters as $filter_id => $handler) {
  58. // Skip non-exposed filters.
  59. if (!$handler->is_exposed()) {
  60. continue;
  61. }
  62. // Required filters can't be dependent.
  63. if ($type == 'dependent' && $handler->options['expose']['required']) {
  64. continue;
  65. }
  66. // Note if we get to ourselves and skip.
  67. if ($filter_id == $this_id) {
  68. $seen = TRUE;
  69. continue;
  70. }
  71. // Skip other instances of this filter.
  72. if ($handler->definition['handler'] == 'views_dependent_filters_handler_filter_dependent') {
  73. continue;
  74. }
  75. $label = $handler->ui_name(TRUE);
  76. // All filters may be controllers, but to simplify things we just allow
  77. // the ones that come before us.
  78. if (!$seen) {
  79. $filters_controller[$filter_id] = $label;
  80. }
  81. // Only filters that follow us in the order may be dependent.
  82. if ($seen) {
  83. $filters_dependent[$filter_id] = $label;
  84. }
  85. }
  86. switch ($type) {
  87. case 'controller':
  88. return $filters_controller;
  89. case 'dependent':
  90. return $filters_dependent;
  91. }
  92. }
  93. /**
  94. * Provide the basic form which calls through to subforms.
  95. * If overridden, it is best to call through to the parent,
  96. * or to at least make sure all of the functions in this form
  97. * are called.
  98. */
  99. function options_form(&$form, &$form_state) {
  100. parent::options_form($form, $form_state);
  101. // Lock the exposed checkbox.
  102. $form['expose_button']['checkbox']['checkbox']['#disabled'] = TRUE;
  103. $form['expose_button']['checkbox']['checkbox']['#description'] = t('This filter is always exposed.');
  104. // Not sure what the 'expose' button is for as there's the checkbox, but
  105. // it's not wanted here.
  106. unset($form['expose_button']['markup']);
  107. unset($form['expose_button']['button']);
  108. $filters = $this->view->display_handler->handlers['filter'];
  109. if (isset($this->options['controller_filter'])) {
  110. // Get the handler for the controller filter.
  111. $controller_filter = $filters[$this->options['controller_filter']];
  112. // Take copies of the form arrays to pass to the other handler.
  113. $form_copy = $form;
  114. $form_state_copy = $form_state;
  115. // Fixup the form so the handler is fooled.
  116. // For some reason we need to add this for non-ajax admin operation.
  117. $form_copy['operator']['#type'] = '';
  118. // Get the value form from the filter handler.
  119. $controller_filter->value_form($form_copy, $form_state);
  120. $controller_values_element = $form_copy['value'];
  121. // Clean up the form element.
  122. if ($controller_values_element['#type'] == 'checkboxes') {
  123. // We have to unset the 'select all' option on checkboxes.
  124. unset($controller_values_element['#options']['all']);
  125. // Force multiple.
  126. $controller_values_element['#multiple'] = TRUE;
  127. }
  128. // Add it to our own form element in the real form.
  129. $form['controller_values'] = array(
  130. '#title' => t('Controller values'),
  131. '#description' => t('The values on the controller filter that will cause the dependent filters to be visible.'),
  132. '#default_value' => isset($this->options['controller_values']) ? $this->options['controller_values'] : array(),
  133. ) + $controller_values_element;
  134. }
  135. $options = $this->get_filter_options('dependent');
  136. $form['dependent_filters'] = array(
  137. '#type' => 'checkboxes',
  138. '#title' => t('Dependent filters'),
  139. '#options' => $options,
  140. '#default_value' => isset($this->options['dependent_filters']) ? $this->options['dependent_filters'] : array(),
  141. '#description' => t('The filters which should only be visible and active when the controller filter has the given values.'),
  142. );
  143. if (empty($options)) {
  144. $form['dependent_filters']['#description'] .= ' ' . t('This filter needs other filters to be placed below it in the order to use as dependents.');
  145. }
  146. }
  147. function has_extra_options() { return TRUE; }
  148. /**
  149. * Provide defaults for the handler.
  150. */
  151. function extra_options(&$option) { }
  152. /**
  153. * Extra settings form: select the controller filter.
  154. *
  155. * Selecting the controller filter here allows us to nicely show its value
  156. * form in the regular options form.
  157. */
  158. function extra_options_form(&$form, &$form_state) {
  159. $options = $this->get_filter_options('controller');
  160. $form['controller_filter'] = array(
  161. '#type' => 'radios',
  162. '#title' => t('Controller filter'),
  163. '#options' => $options,
  164. '#default_value' => isset($this->options['controller_filter']) ? $this->options['controller_filter'] : '',
  165. '#description' => t('The exposed filter whose values will be used to control dependent filters. Only filters that are prior to this one in the order are allowed.'),
  166. );
  167. }
  168. /**
  169. * Override the options form exposed options subform to show nothing, as the
  170. * options here don't make sense for us.
  171. */
  172. function expose_form(&$form, &$form_state) { return; }
  173. /**
  174. * Display the filter on the administrative summary.
  175. */
  176. function admin_summary() {
  177. $controller_filter = $this->options['controller_filter'];
  178. $dependent_filters = implode(', ', array_filter($this->options['dependent_filters']));
  179. return t("@controller controlling @dependents", array(
  180. '@controller' => $controller_filter,
  181. '@dependents' => $dependent_filters,
  182. ));
  183. }
  184. /**
  185. * Make our changes to the form but don't return anything ourselves.
  186. */
  187. function exposed_form(&$form, &$form_state) {
  188. // Build an array of dependency info.
  189. $dependency_info = array(
  190. // An array keyed by controller filter IDs, where the values are arrays
  191. // of their possible values.
  192. // In practice there is only one controller filter, but technically there
  193. // could be several. The problem is that the admin UI to set them up
  194. // would become a nightmare, and there's the matter of whether to combine
  195. // them with AND or OR. Hence one for later, if ever required.
  196. 'controllers' => array(),
  197. // An array of dependent filter IDs.
  198. 'dependents' => array(),
  199. );
  200. if (!empty($this->options['controller_filter'])) {
  201. $controller_filter = $this->options['controller_filter'];
  202. $dependency_info['controllers'][$controller_filter] = array();
  203. if (!empty($this->options['controller_values'])) {
  204. if (is_array($this->options['controller_values'])) {
  205. // Filter out the crud from Form API checkboxes and get rid of the
  206. // keys to avoid confusion: we compare on values further down.
  207. $controller_values = array_values(array_filter($this->options['controller_values']));
  208. }
  209. else {
  210. $controller_values = array($this->options['controller_values']);
  211. }
  212. $dependency_info['controllers'][$controller_filter] = $controller_values;
  213. }
  214. }
  215. $dependency_info['dependents'] = array_values(array_filter($this->options['dependent_filters']));
  216. //dsm($form_state['input'], 'input');
  217. foreach ($dependency_info['controllers'] as $filter_id => $controller_values) {
  218. // Get the input for this filter.
  219. $input = $form_state['input'][$filter_id];
  220. // Convert values for non-multiple filters to an array.
  221. if (!$this->view->filter[$filter_id]->options['expose']['multiple']) {
  222. $input = array($input);
  223. }
  224. $intersection = array_intersect($input, $controller_values);
  225. if (!count($intersection)) {
  226. $this->filters_kill = $dependency_info['dependents'];
  227. }
  228. }
  229. // We can kill the dependent filters now.
  230. // $this->filters_disable();
  231. // ...alternatively, leave them there so their form is shown, but prevent
  232. // them from collecting input.
  233. // This means the form element can be subject to CTools dependent visiblity
  234. // and means the user can refine their filtering without an interim
  235. // submission of the form.
  236. // @todo: Allow this as an option, ie have a 'no js' version which would
  237. // just kill dependent filters now.
  238. // To make the dependent filters change their visibility we need to add a
  239. // CTools dependent property, but we can't do that here as the form
  240. // elements for these don't exist yet.
  241. // Only way to do this is to register an #after_build on the whole form
  242. // which lives in module code rather than in this handler.
  243. // Add our settings to the form state as an array, as we need to account
  244. // for the possiblity that more than one copy of this handler may be
  245. // playing at once!
  246. $form_state['dependent_exposed_filters'][] = $dependency_info;
  247. $form['#after_build'] = array('views_dependent_filters_exposed_form_after_build');
  248. // Some clean-up for things that come later.
  249. // Mark ourselves not being exposed now we've done our work. This isn't
  250. // necessary for Views itself, but allows compatibility with the
  251. // better_exposed_filters module whose exposed_form_alter() tries to work
  252. // with all exposed filters.
  253. $this->options['exposed'] = FALSE;
  254. // We do nada to the form ourselves.
  255. return;
  256. }
  257. /**
  258. * Doctor this so the whole form doesn't add our element to $form['#info'].
  259. *
  260. * @see views_exposed_form().
  261. */
  262. function exposed_info() {
  263. return;
  264. }
  265. /**
  266. * Prevent the view from accepting input from ourselves and dependents.
  267. */
  268. function accept_exposed_input($input) {
  269. // Disable our dependent filters just before they have a chance to act
  270. // on exposed input.
  271. $this->filters_disable();
  272. // Doctor this so the whole form doesn't go looking for our exposed input.
  273. return TRUE;
  274. }
  275. function value_form(&$form, &$form_state) {
  276. // Return nothing for the value form.
  277. $form['value'] = array();
  278. }
  279. function query() {
  280. // Do nothing: fake filter.
  281. }
  282. }