123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- <?php
- /**
- *
- */
- class views_dependent_filters_handler_filter_dependent extends views_handler_filter {
- /**
- * The list of filters subsequent to ourselves that we should remove.
- * An array of keys of $this->view->filter.
- */
- var $filters_kill = array();
- /**
- * Disable the filters we control.
- */
- function filters_disable() {
- // This hacks subsequent handlers' options so they are no longer exposed.
- // Incoming values from these on submit will be simply ignored.
- foreach ($this->filters_kill as $filter_id) {
- $this->view->filter[$filter_id]->options['exposed'] = FALSE;
- }
- }
- /**
- * Enable the filters we control.
- */
- function filters_enable() {
- foreach ($this->filters_kill as $filter_id) {
- $this->view->filter[$filter_id]->options['exposed'] = TRUE;
- }
- }
- function option_definition() {
- $options = parent::option_definition();
- // Override the exposed default. This makes no sense not exposed.
- $options['exposed'] = array('default' => TRUE);
- $options['controller_filter'] = array('default' => NULL);
- $options['controller_values'] = array('default' => NULL);
- $options['dependent_filters'] = array('default' => array());
- return $options;
- }
- /**
- * Helper function to provide form options for lists of filters.
- *
- * @param $type
- * One of 'controller' or 'dependent'.
- *
- * @return
- * An array of filters suitable for use as Form API options.
- */
- function get_filter_options($type) {
- // Due to http://drupal.org/node/1426094 we can't just go looking in the
- // handlers array on the display.
- $filters = $this->view->display_handler->get_handlers('filter');
- // Get the unique id of this handler (ie allow more than one of this handler).
- $this_id = $this->options['id'];
- $filters_controller = array();
- $filters_dependent = array();
- $seen = FALSE;
- // Build up the options from all the fields up to this one but no further.
- foreach ($filters as $filter_id => $handler) {
- // Skip non-exposed filters.
- if (!$handler->is_exposed()) {
- continue;
- }
- // Required filters can't be dependent.
- if ($type == 'dependent' && $handler->options['expose']['required']) {
- continue;
- }
- // Note if we get to ourselves and skip.
- if ($filter_id == $this_id) {
- $seen = TRUE;
- continue;
- }
- // Skip other instances of this filter.
- if ($handler->definition['handler'] == 'views_dependent_filters_handler_filter_dependent') {
- continue;
- }
- $label = $handler->ui_name(TRUE);
- // All filters may be controllers, but to simplify things we just allow
- // the ones that come before us.
- if (!$seen) {
- $filters_controller[$filter_id] = $label;
- }
- // Only filters that follow us in the order may be dependent.
- if ($seen) {
- $filters_dependent[$filter_id] = $label;
- }
- }
- switch ($type) {
- case 'controller':
- return $filters_controller;
- case 'dependent':
- return $filters_dependent;
- }
- }
- /**
- * Provide the basic form which calls through to subforms.
- * If overridden, it is best to call through to the parent,
- * or to at least make sure all of the functions in this form
- * are called.
- */
- function options_form(&$form, &$form_state) {
- parent::options_form($form, $form_state);
- // Lock the exposed checkbox.
- $form['expose_button']['checkbox']['checkbox']['#disabled'] = TRUE;
- $form['expose_button']['checkbox']['checkbox']['#description'] = t('This filter is always exposed.');
- // Not sure what the 'expose' button is for as there's the checkbox, but
- // it's not wanted here.
- unset($form['expose_button']['markup']);
- unset($form['expose_button']['button']);
- $filters = $this->view->display_handler->handlers['filter'];
- if (isset($this->options['controller_filter'])) {
- // Get the handler for the controller filter.
- $controller_filter = $filters[$this->options['controller_filter']];
- // Take copies of the form arrays to pass to the other handler.
- $form_copy = $form;
- $form_state_copy = $form_state;
- // Fixup the form so the handler is fooled.
- // For some reason we need to add this for non-ajax admin operation.
- $form_copy['operator']['#type'] = '';
- // Get the value form from the filter handler.
- $controller_filter->value_form($form_copy, $form_state);
- $controller_values_element = $form_copy['value'];
- // Clean up the form element.
- if ($controller_values_element['#type'] == 'checkboxes') {
- // We have to unset the 'select all' option on checkboxes.
- unset($controller_values_element['#options']['all']);
- // Force multiple.
- $controller_values_element['#multiple'] = TRUE;
- }
- // Add it to our own form element in the real form.
- $form['controller_values'] = array(
- '#title' => t('Controller values'),
- '#description' => t('The values on the controller filter that will cause the dependent filters to be visible.'),
- '#default_value' => isset($this->options['controller_values']) ? $this->options['controller_values'] : array(),
- ) + $controller_values_element;
- }
- $options = $this->get_filter_options('dependent');
- $form['dependent_filters'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Dependent filters'),
- '#options' => $options,
- '#default_value' => isset($this->options['dependent_filters']) ? $this->options['dependent_filters'] : array(),
- '#description' => t('The filters which should only be visible and active when the controller filter has the given values.'),
- );
- if (empty($options)) {
- $form['dependent_filters']['#description'] .= ' ' . t('This filter needs other filters to be placed below it in the order to use as dependents.');
- }
- }
- function has_extra_options() { return TRUE; }
- /**
- * Provide defaults for the handler.
- */
- function extra_options(&$option) { }
- /**
- * Extra settings form: select the controller filter.
- *
- * Selecting the controller filter here allows us to nicely show its value
- * form in the regular options form.
- */
- function extra_options_form(&$form, &$form_state) {
- $options = $this->get_filter_options('controller');
- $form['controller_filter'] = array(
- '#type' => 'radios',
- '#title' => t('Controller filter'),
- '#options' => $options,
- '#default_value' => isset($this->options['controller_filter']) ? $this->options['controller_filter'] : '',
- '#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.'),
- );
- }
- /**
- * Override the options form exposed options subform to show nothing, as the
- * options here don't make sense for us.
- */
- function expose_form(&$form, &$form_state) { return; }
- /**
- * Display the filter on the administrative summary.
- */
- function admin_summary() {
- $controller_filter = $this->options['controller_filter'];
- $dependent_filters = implode(', ', array_filter($this->options['dependent_filters']));
- return t("@controller controlling @dependents", array(
- '@controller' => $controller_filter,
- '@dependents' => $dependent_filters,
- ));
- }
- /**
- * Make our changes to the form but don't return anything ourselves.
- */
- function exposed_form(&$form, &$form_state) {
- // Build an array of dependency info.
- $dependency_info = array(
- // An array keyed by controller filter IDs, where the values are arrays
- // of their possible values.
- // In practice there is only one controller filter, but technically there
- // could be several. The problem is that the admin UI to set them up
- // would become a nightmare, and there's the matter of whether to combine
- // them with AND or OR. Hence one for later, if ever required.
- 'controllers' => array(),
- // An array of dependent filter IDs.
- 'dependents' => array(),
- );
- if (!empty($this->options['controller_filter'])) {
- $controller_filter = $this->options['controller_filter'];
- $dependency_info['controllers'][$controller_filter] = array();
- if (!empty($this->options['controller_values'])) {
- if (is_array($this->options['controller_values'])) {
- // Filter out the crud from Form API checkboxes and get rid of the
- // keys to avoid confusion: we compare on values further down.
- $controller_values = array_values(array_filter($this->options['controller_values']));
- }
- else {
- $controller_values = array($this->options['controller_values']);
- }
- $dependency_info['controllers'][$controller_filter] = $controller_values;
- }
- }
- $dependency_info['dependents'] = array_values(array_filter($this->options['dependent_filters']));
- //dsm($form_state['input'], 'input');
- foreach ($dependency_info['controllers'] as $filter_id => $controller_values) {
- // Get the input for this filter.
- $input = $form_state['input'][$filter_id];
- // Convert values for non-multiple filters to an array.
- if (!$this->view->filter[$filter_id]->options['expose']['multiple']) {
- $input = array($input);
- }
- $intersection = array_intersect($input, $controller_values);
- if (!count($intersection)) {
- $this->filters_kill = $dependency_info['dependents'];
- }
- }
- // We can kill the dependent filters now.
- // $this->filters_disable();
- // ...alternatively, leave them there so their form is shown, but prevent
- // them from collecting input.
- // This means the form element can be subject to CTools dependent visiblity
- // and means the user can refine their filtering without an interim
- // submission of the form.
- // @todo: Allow this as an option, ie have a 'no js' version which would
- // just kill dependent filters now.
- // To make the dependent filters change their visibility we need to add a
- // CTools dependent property, but we can't do that here as the form
- // elements for these don't exist yet.
- // Only way to do this is to register an #after_build on the whole form
- // which lives in module code rather than in this handler.
- // Add our settings to the form state as an array, as we need to account
- // for the possiblity that more than one copy of this handler may be
- // playing at once!
- $form_state['dependent_exposed_filters'][] = $dependency_info;
- $form['#after_build'] = array('views_dependent_filters_exposed_form_after_build');
- // Some clean-up for things that come later.
- // Mark ourselves not being exposed now we've done our work. This isn't
- // necessary for Views itself, but allows compatibility with the
- // better_exposed_filters module whose exposed_form_alter() tries to work
- // with all exposed filters.
- $this->options['exposed'] = FALSE;
- // We do nada to the form ourselves.
- return;
- }
- /**
- * Doctor this so the whole form doesn't add our element to $form['#info'].
- *
- * @see views_exposed_form().
- */
- function exposed_info() {
- return;
- }
- /**
- * Prevent the view from accepting input from ourselves and dependents.
- */
- function accept_exposed_input($input) {
- // Disable our dependent filters just before they have a chance to act
- // on exposed input.
- $this->filters_disable();
- // Doctor this so the whole form doesn't go looking for our exposed input.
- return TRUE;
- }
- function value_form(&$form, &$form_state) {
- // Return nothing for the value form.
- $form['value'] = array();
- }
- function query() {
- // Do nothing: fake filter.
- }
- }
|