Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / views / src / Plugin / views / filter / BooleanOperator.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\filter;
4
5 use Drupal\Core\Database\Query\Condition;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\views\Plugin\views\display\DisplayPluginBase;
8 use Drupal\views\ViewExecutable;
9
10 /**
11  * Simple filter to handle matching of boolean values
12  *
13  * Definition items:
14  * - label: (REQUIRED) The label for the checkbox.
15  * - type: For basic 'true false' types, an item can specify the following:
16  *    - true-false: True/false (this is the default)
17  *    - yes-no: Yes/No
18  *    - on-off: On/Off
19  *    - enabled-disabled: Enabled/Disabled
20  * - accept null: Treat a NULL value as false.
21  * - use_equal: If you use this flag the query will use = 1 instead of <> 0.
22  *   This might be helpful for performance reasons.
23  *
24  * @ingroup views_filter_handlers
25  *
26  * @ViewsFilter("boolean")
27  */
28 class BooleanOperator extends FilterPluginBase {
29
30   /**
31    * The equal query operator.
32    *
33    * @var string
34    */
35   const EQUAL = '=';
36
37   /**
38    * The non equal query operator.
39    *
40    * @var string
41    */
42   const NOT_EQUAL = '<>';
43
44   // exposed filter options
45   protected $alwaysMultiple = TRUE;
46   // Don't display empty space where the operator would be.
47   public $no_operator = TRUE;
48   // Whether to accept NULL as a false value or not
49   public $accept_null = FALSE;
50
51
52   /**
53    * {@inheritdoc}
54    */
55   public function operatorOptions($which = 'title') {
56     $options = [];
57     foreach ($this->operators() as $id => $info) {
58       $options[$id] = $info[$which];
59     }
60
61     return $options;
62   }
63
64   /**
65    * Returns an array of operator information.
66    *
67    * @return array
68    */
69   protected function operators() {
70     return [
71       '=' => [
72         'title' => $this->t('Is equal to'),
73         'method' => 'queryOpBoolean',
74         'short' => $this->t('='),
75         'values' => 1,
76         'query_operator' => self::EQUAL,
77       ],
78       '!=' => [
79         'title' => $this->t('Is not equal to'),
80         'method' => 'queryOpBoolean',
81         'short' => $this->t('!='),
82         'values' => 1,
83         'query_operator' => self::NOT_EQUAL,
84       ],
85     ];
86   }
87
88   /**
89    * {@inheritdoc}
90    */
91   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
92     parent::init($view, $display, $options);
93
94     $this->value_value = $this->t('True');
95
96     if (isset($this->definition['label'])) {
97       $this->value_value = $this->definition['label'];
98     }
99     elseif (isset($this->definition['title'])) {
100       $this->value_value = $this->definition['title'];
101     }
102
103     if (isset($this->definition['accept null'])) {
104       $this->accept_null = (bool) $this->definition['accept null'];
105     }
106     elseif (isset($this->definition['accept_null'])) {
107       $this->accept_null = (bool) $this->definition['accept_null'];
108     }
109     $this->valueOptions = NULL;
110   }
111
112   /**
113    * Return the possible options for this filter.
114    *
115    * Child classes should override this function to set the possible values
116    * for the filter.  Since this is a boolean filter, the array should have
117    * two possible keys: 1 for "True" and 0 for "False", although the labels
118    * can be whatever makes sense for the filter.  These values are used for
119    * configuring the filter, when the filter is exposed, and in the admin
120    * summary of the filter.  Normally, this should be static data, but if it's
121    * dynamic for some reason, child classes should use a guard to reduce
122    * database hits as much as possible.
123    */
124   public function getValueOptions() {
125     if (isset($this->definition['type'])) {
126       if ($this->definition['type'] == 'yes-no') {
127         $this->valueOptions = [1 => $this->t('Yes'), 0 => $this->t('No')];
128       }
129       if ($this->definition['type'] == 'on-off') {
130         $this->valueOptions = [1 => $this->t('On'), 0 => $this->t('Off')];
131       }
132       if ($this->definition['type'] == 'enabled-disabled') {
133         $this->valueOptions = [1 => $this->t('Enabled'), 0 => $this->t('Disabled')];
134       }
135     }
136
137     // Provide a fallback if the above didn't set anything.
138     if (!isset($this->valueOptions)) {
139       $this->valueOptions = [1 => $this->t('True'), 0 => $this->t('False')];
140     }
141   }
142
143   protected function defineOptions() {
144     $options = parent::defineOptions();
145
146     $options['value']['default'] = FALSE;
147
148     return $options;
149   }
150
151   protected function valueForm(&$form, FormStateInterface $form_state) {
152     if (empty($this->valueOptions)) {
153       // Initialize the array of possible values for this filter.
154       $this->getValueOptions();
155     }
156     if ($exposed = $form_state->get('exposed')) {
157       // Exposed filter: use a select box to save space.
158       $filter_form_type = 'select';
159     }
160     else {
161       // Configuring a filter: use radios for clarity.
162       $filter_form_type = 'radios';
163     }
164     $form['value'] = [
165       '#type' => $filter_form_type,
166       '#title' => $this->value_value,
167       '#options' => $this->valueOptions,
168       '#default_value' => $this->value,
169     ];
170     if (!empty($this->options['exposed'])) {
171       $identifier = $this->options['expose']['identifier'];
172       $user_input = $form_state->getUserInput();
173       if ($exposed && !isset($user_input[$identifier])) {
174         $user_input[$identifier] = $this->value;
175         $form_state->setUserInput($user_input);
176       }
177       // If we're configuring an exposed filter, add an - Any - option.
178       if (!$exposed || empty($this->options['expose']['required'])) {
179         $form['value']['#options'] = ['All' => $this->t('- Any -')] + $form['value']['#options'];
180       }
181     }
182   }
183
184   protected function valueValidate($form, FormStateInterface $form_state) {
185     if ($form_state->getValue(['options', 'value']) == 'All' && !$form_state->isValueEmpty(['options', 'expose', 'required'])) {
186       $form_state->setErrorByName('value', $this->t('You must select a value unless this is an non-required exposed filter.'));
187     }
188   }
189
190   public function adminSummary() {
191     if ($this->isAGroup()) {
192       return $this->t('grouped');
193     }
194     if (!empty($this->options['exposed'])) {
195       return $this->t('exposed');
196     }
197     if (empty($this->valueOptions)) {
198       $this->getValueOptions();
199     }
200     // Now that we have the valid options for this filter, just return the
201     // human-readable label based on the current value.  The valueOptions
202     // array is keyed with either 0 or 1, so if the current value is not
203     // empty, use the label for 1, and if it's empty, use the label for 0.
204     return $this->operator . ' ' . $this->valueOptions[!empty($this->value)];
205   }
206
207   public function defaultExposeOptions() {
208     parent::defaultExposeOptions();
209     $this->options['expose']['operator_id'] = '';
210     $this->options['expose']['label'] = $this->value_value;
211     $this->options['expose']['required'] = TRUE;
212   }
213
214   /**
215    * {@inheritdoc}
216    */
217   public function query() {
218     $this->ensureMyTable();
219     $field = "$this->tableAlias.$this->realField";
220
221     $info = $this->operators();
222     if (!empty($info[$this->operator]['method'])) {
223       call_user_func([$this, $info[$this->operator]['method']], $field, $info[$this->operator]['query_operator']);
224     }
225   }
226
227   /**
228    * Adds a where condition to the query for a boolean value.
229    *
230    * @param string $field
231    *   The field name to add the where condition for.
232    * @param string $query_operator
233    *   (optional) Either self::EQUAL or self::NOT_EQUAL. Defaults to
234    *   self::EQUAL.
235    */
236   protected function queryOpBoolean($field, $query_operator = self::EQUAL) {
237     if (empty($this->value)) {
238       if ($this->accept_null) {
239         if ($query_operator === self::EQUAL) {
240           $condition = (new Condition('OR'))
241             ->condition($field, 0, $query_operator)
242             ->isNull($field);
243         }
244         else {
245           $condition = (new Condition('AND'))
246             ->condition($field, 0, $query_operator)
247             ->isNotNull($field);
248         }
249         $this->query->addWhere($this->options['group'], $condition);
250       }
251       else {
252         $this->query->addWhere($this->options['group'], $field, 0, $query_operator);
253       }
254     }
255     else {
256       if (!empty($this->definition['use_equal'])) {
257         // Forces a self::EQUAL operator instead of a self::NOT_EQUAL for
258         // performance reasons.
259         if ($query_operator === self::EQUAL) {
260           $this->query->addWhere($this->options['group'], $field, 1, self::EQUAL);
261         }
262         else {
263           $this->query->addWhere($this->options['group'], $field, 0, self::EQUAL);
264         }
265       }
266       else {
267         $this->query->addWhere($this->options['group'], $field, 1, $query_operator);
268       }
269     }
270   }
271
272 }