InOperator.php
Same filename in other branches
Namespace
Drupal\views\Plugin\views\filterFile
-
core/
modules/ views/ src/ Plugin/ views/ filter/ InOperator.php
View source
<?php
namespace Drupal\views\Plugin\views\filter;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Attribute\ViewsFilter;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Drupal\Core\Form\OptGroup;
/**
* Simple filter to handle matching of multiple options selectable via checkboxes.
*
* Definition items:
* - options callback: The function to call in order to generate the value options. If omitted, the options 'Yes' and 'No' will be used.
* - options arguments: An array of arguments to pass to the options callback.
*
* @ingroup views_filter_handlers
*/
class InOperator extends FilterPluginBase implements FilterOperatorsInterface {
protected $valueFormType = 'checkboxes';
/**
* @var array
* Stores all operations which are available on the form.
*/
protected $valueOptions = NULL;
/**
* The filter title.
*
* @var string
*/
protected $valueTitle;
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
parent::init($view, $display, $options);
$this->valueTitle = $this->t('Options');
$this->valueOptions = NULL;
}
/**
* Gets the value options.
*
* Child classes should be used to override this function and set the
* 'value options', unless 'options callback' is defined as a valid function
* or static public method to generate these values.
*
* This can use a guard to be used to reduce database hits as much as
* possible.
*
* @return array|null
* The stored values from $this->valueOptions.
*/
public function getValueOptions() {
if (isset($this->valueOptions)) {
return $this->valueOptions;
}
if (isset($this->definition['options callback']) && is_callable($this->definition['options callback'])) {
if (isset($this->definition['options arguments']) && is_array($this->definition['options arguments'])) {
$this->valueOptions = call_user_func_array($this->definition['options callback'], $this->definition['options arguments']);
}
else {
$this->valueOptions = call_user_func($this->definition['options callback']);
}
}
else {
$this->valueOptions = [
$this->t('Yes'),
$this->t('No'),
];
}
return $this->valueOptions;
}
public function defaultExposeOptions() {
parent::defaultExposeOptions();
$this->options['expose']['reduce'] = FALSE;
}
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
$form['expose']['reduce'] = [
'#type' => 'checkbox',
'#title' => $this->t('Limit list to selected items'),
'#description' => $this->t('If checked, the only items presented to the user will be the ones selected here.'),
// Safety.
'#default_value' => !empty($this->options['expose']['reduce']),
];
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['operator']['default'] = 'in';
$options['value']['default'] = [];
$options['expose']['contains']['reduce'] = [
'default' => FALSE,
];
return $options;
}
/**
* {@inheritdoc}
*/
public function operators() {
$operators = [
'in' => [
'title' => $this->t('Is one of'),
'short' => $this->t('in'),
'short_single' => $this->t('='),
'method' => 'opSimple',
'values' => 1,
],
'not in' => [
'title' => $this->t('Is not one of'),
'short' => $this->t('not in'),
'short_single' => $this->t('<>'),
'method' => 'opSimple',
'values' => 1,
],
];
// If the definition allows for the empty operator, add it.
if (!empty($this->definition['allow empty'])) {
$operators += [
'empty' => [
'title' => $this->t('Is empty (NULL)'),
'method' => 'opEmpty',
'short' => $this->t('empty'),
'values' => 0,
],
'not empty' => [
'title' => $this->t('Is not empty (NOT NULL)'),
'method' => 'opEmpty',
'short' => $this->t('not empty'),
'values' => 0,
],
];
}
return $operators;
}
/**
* Build strings from the operators() for 'select' options.
*/
public function operatorOptions($which = 'title') {
$options = [];
foreach ($this->operators() as $id => $info) {
$options[$id] = $info[$which];
}
return $options;
}
protected function operatorValues($values = 1) {
$options = [];
foreach ($this->operators() as $id => $info) {
if (isset($info['values']) && $info['values'] == $values) {
$options[] = $id;
}
}
return $options;
}
protected function valueForm(&$form, FormStateInterface $form_state) {
$form['value'] = [];
$options = [];
$exposed = $form_state->get('exposed');
if (!$exposed) {
// Add a select all option to the value form.
$options = [
'all' => $this->t('Select all'),
];
}
$this->getValueOptions();
$options += $this->valueOptions;
$default_value = (array) $this->value;
$which = 'all';
if (!empty($form['operator'])) {
$source = ':input[name="options[operator]"]';
}
if ($exposed) {
$identifier = $this->options['expose']['identifier'];
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// Exposed and locked.
$which = in_array($this->operator, $this->operatorValues(1)) ? 'value' : 'none';
}
else {
$source = ':input[name="' . $this->options['expose']['operator_id'] . '"]';
}
if (!empty($this->options['expose']['reduce'])) {
$options = $this->reduceValueOptions();
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
$default_value = [];
}
}
if (empty($this->options['expose']['multiple'])) {
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce'])) || isset($this->options['value']['all'])) {
$default_value = 'All';
}
elseif (empty($default_value)) {
$keys = array_keys($options);
$default_value = array_shift($keys);
}
else {
$copy = $default_value;
$default_value = array_shift($copy);
}
}
}
if ($which == 'all' || $which == 'value') {
$form['value'] = [
'#type' => $this->valueFormType,
'#title' => $this->valueTitle,
'#options' => $options,
'#default_value' => $default_value,
// These are only valid for 'select' type, but do no harm to checkboxes.
'#multiple' => TRUE,
// The value options can be a multidimensional array if the value form
// type is a select list, so make sure that they are counted correctly.
'#size' => min(count($options, COUNT_RECURSIVE), 8),
];
$user_input = $form_state->getUserInput();
if ($exposed && !isset($user_input[$identifier])) {
$user_input[$identifier] = $default_value;
$form_state->setUserInput($user_input);
}
if ($which == 'all') {
if (!$exposed && in_array($this->valueFormType, [
'checkbox',
'checkboxes',
'radios',
'select',
])) {
$form['value']['#prefix'] = '<div id="edit-options-value-wrapper">';
$form['value']['#suffix'] = '</div>';
}
// Setup #states for all operators with one value.
foreach ($this->operatorValues(1) as $operator) {
$form['value']['#states']['visible'][] = [
$source => [
'value' => $operator,
],
];
}
}
}
}
/**
* When using exposed filters, we may be required to reduce the set.
*/
public function reduceValueOptions($input = NULL) {
if (!isset($input)) {
$input = $this->valueOptions;
}
// Because options may be an array of strings, or an array of mixed arrays
// and strings (optgroups), or an array of objects, or a form of Markup, we
// have to step through and handle each one individually.
$options = [];
foreach ($input as $id => $option) {
if (is_array($option)) {
$options[$id] = $this->reduceValueOptions($option);
continue;
}
elseif (is_object($option) && !$option instanceof MarkupInterface) {
$keys = array_keys($option->option);
$key = array_shift($keys);
if (isset($this->options['value'][$key])) {
$options[$id] = $option;
}
}
elseif (isset($this->options['value'][$id])) {
$options[$id] = $option;
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function acceptExposedInput($input) {
if (empty($this->options['exposed'])) {
return TRUE;
}
// The "All" state for this type of filter could have a default value. If
// this is a non-multiple and non-required option, then this filter will
// participate by using the default settings *if* 'limit' is true.
if (empty($this->options['expose']['multiple']) && empty($this->options['expose']['required']) && !empty($this->options['expose']['limit'])) {
$identifier = $this->options['expose']['identifier'];
if ($input[$identifier] == 'All') {
return TRUE;
}
}
return parent::acceptExposedInput($input);
}
protected function valueSubmit($form, FormStateInterface $form_state) {
// Drupal's FAPI system automatically puts '0' in for any checkbox that
// was not set, and the key to the checkbox if it is set.
// Unfortunately, this means that if the key to that checkbox is 0,
// we are unable to tell if that checkbox was set or not.
// Luckily, the '#value' on the checkboxes form actually contains
// *only* a list of checkboxes that were set, and we can use that
// instead.
$form_state->setValue([
'options',
'value',
], $form['value']['#value']);
}
public function adminSummary() {
if ($this->isAGroup()) {
return $this->t('grouped');
}
if (!empty($this->options['exposed'])) {
return $this->t('exposed');
}
$info = $this->operators();
$this->getValueOptions();
// Some filter_in_operator usage uses optgroups forms, so flatten it.
$flat_options = OptGroup::flattenOptions($this->valueOptions);
if (!is_array($this->value)) {
return;
}
$operator = $info[$this->operator]['short'];
$values = '';
if (in_array($this->operator, $this->operatorValues(1))) {
// Remove every element which is not known.
foreach ($this->value as $value) {
if (!isset($flat_options[$value])) {
unset($this->value[$value]);
}
}
// Choose different kind of output for 0, a single and multiple values.
if (count($this->value) == 0) {
$values = $this->t('Unknown');
}
elseif (count($this->value) == 1) {
// If any, use the 'single' short name of the operator instead.
if (isset($info[$this->operator]['short_single'])) {
$operator = $info[$this->operator]['short_single'];
}
$keys = $this->value;
$value = array_shift($keys);
if (isset($flat_options[$value])) {
$values = $flat_options[$value];
}
else {
$values = '';
}
}
else {
foreach ($this->value as $value) {
if ($values !== '') {
$values .= ', ';
}
if (mb_strlen($values) > 8) {
$values = Unicode::truncate($values, 8, FALSE, TRUE);
break;
}
if (isset($flat_options[$value])) {
$values .= $flat_options[$value];
}
}
}
}
return $operator . ($values !== '' ? ' ' . $values : '');
}
public function query() {
$info = $this->operators();
if (!empty($info[$this->operator]['method'])) {
$this->{$info[$this->operator]['method']}();
}
}
protected function opSimple() {
if (empty($this->value)) {
return;
}
$this->ensureMyTable();
// We use array_values() because the checkboxes keep keys and that can cause
// array addition problems.
$this->query
->addWhere($this->options['group'], "{$this->tableAlias}.{$this->realField}", array_values($this->value), $this->operator);
}
protected function opEmpty() {
$this->ensureMyTable();
if ($this->operator == 'empty') {
$operator = "IS NULL";
}
else {
$operator = "IS NOT NULL";
}
$this->query
->addWhere($this->options['group'], "{$this->tableAlias}.{$this->realField}", NULL, $operator);
}
public function validate() {
$this->getValueOptions();
$errors = parent::validate();
// If the operator is an operator which doesn't require a value, there is
// no need for additional validation.
if (in_array($this->operator, $this->operatorValues(0))) {
return [];
}
if (!in_array($this->operator, $this->operatorValues(1))) {
$errors[] = $this->t('The operator is invalid on filter: @filter.', [
'@filter' => $this->adminLabel(TRUE),
]);
}
if (is_array($this->value)) {
if (!isset($this->valueOptions)) {
// Don't validate if there are none value options provided, for example for special handlers.
return $errors;
}
if ($this->options['exposed'] && !$this->options['expose']['required'] && empty($this->value)) {
// Don't validate if the field is exposed and no default value is provided.
return $errors;
}
// Some filter_in_operator usage uses optgroups forms, so flatten it.
$flat_options = OptGroup::flattenOptions($this->valueOptions);
// Remove every element which is not known.
foreach ($this->value as $value) {
if (!isset($flat_options[$value])) {
unset($this->value[$value]);
}
}
// Choose different kind of output for 0, a single and multiple values.
if (count($this->value) == 0) {
$errors[] = $this->t('No valid values found on filter: @filter.', [
'@filter' => $this->adminLabel(TRUE),
]);
}
}
elseif (!empty($this->value) && ($this->operator == 'in' || $this->operator == 'not in')) {
$errors[] = $this->t('The value @value is not an array for @operator on filter: @filter', [
'@value' => var_export($this->value, TRUE),
'@operator' => $this->operator,
'@filter' => $this->adminLabel(TRUE),
]);
}
return $errors;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
InOperator | Simple filter to handle matching of multiple options selectable via checkboxes. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.