ViewsDependentFilter.php in Views Dependent Filters 8
File
src/Plugin/views/filter/ViewsDependentFilter.phpView source
<?php
namespace Drupal\views_dependent_filter\Plugin\views\filter;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Drupal\Core\Form\OptGroup;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
/**
* EREFNodeTitles.
*/
/**
* Filters by given list of related content title options.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("views_dependent_filter")
*/
class ViewsDependentFilter extends FilterPluginBase {
/**
* 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->display_handler
->getHandlers('filter')[$filter_id]->options['exposed'] = FALSE;
}
}
/**
* Enable the filters we control.
*/
function filters_enable() {
foreach ($this->filters_kill as $filter_id) {
$this->view->display_handler
->getHandlers('filter')[$filter_id]->options['exposed'] = TRUE;
}
}
protected function defineOptions() {
$options = parent::defineOptions();
// 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) {
//kint($this);die();
// 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
->getHandlers('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
->isExposed()) {
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_filter') {
continue;
}
//kint($handler);
//$label = $handler->ui_name(TRUE);
$label = $filter_id;
// 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.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($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
->valueForm($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.');
}
}
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
$filters = $this->view->display_handler
->getHandlers('filter');
$controller_filter = $this->options['controller_filter'];
//kint($controller_filter);
foreach ($filters as $key => $value) {
if ($key == "views_dependent_filter") {
$filters[$key]->options['expose']['identifier'] = 'views_dependent_filter';
}
}
//$identifier = $filters[$controller_filter]->options['expose']['identifier'];
//kint($identifier);
//die();
}
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
}
/**
* If a handler has 'extra options' it will get a little settings widget and
* another form called extra_options.
*/
public function hasExtraOptions() {
return TRUE;
}
/**
* Provide defaults for the handler.
*/
public function defineExtraOptions(&$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.
*/
public function buildExtraOptionsForm(&$form, FormStateInterface $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 showExposeForm(&$form, FormStateInterface $form_state) {
return;
}
/**
* Display the filter on the administrative summary.
*/
function adminSummary() {
$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 buildExposedForm(&$form, FormStateInterface $form_state) {
$filters = $this->view->display_handler
->getHandlers('filter');
// 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(),
// A lookup of filter IDs to filter URL identifiers.
'identifiers' => 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;
$identifier = $filters[$controller_filter]->options['expose']['identifier'];
$dependency_info['identifiers'][$controller_filter] = $identifier;
}
}
$dependency_info['dependents'] = array_values(array_filter($this->options['dependent_filters']));
// Populate the identifiers lookup with our dependent filters.
foreach ($dependency_info['dependents'] as $dependent_filter_id) {
$identifier = $filters[$dependent_filter_id]->options['expose']['identifier'];
$dependency_info['identifiers'][$dependent_filter_id] = $identifier;
}
//dsm($form_state['input'], 'input');
$filters = $this->view->display_handler
->getHandlers('filter');
foreach ($dependency_info['controllers'] as $filter_id => $controller_values) {
// Get the form identifier.
$identifier = $filters[$filter_id]->options['expose']['identifier'];
// Get the input for this filter.
$input =& $form_state
->getUserInput()[$identifier];
// 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 acceptExposedInput($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 valueForm(&$form, FormStateInterface $form_state) {
// Return nothing for the value form.
$form['value'] = array();
}
function query() {
// Do nothing: fake filter.
}
}
Classes
Name | Description |
---|---|
ViewsDependentFilter | Filters by given list of related content title options. |