7bde8ff5c7c8c1977b1b6abe37e2bb355a6ab84d
[yaffs-website] / web / core / modules / views / src / Plugin / views / argument / StringArgument.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\argument;
4
5 use Drupal\Core\Database\Database;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\Core\Plugin\Context\ContextDefinition;
8 use Drupal\views\ViewExecutable;
9 use Drupal\views\Plugin\views\display\DisplayPluginBase;
10 use Drupal\views\ManyToOneHelper;
11
12 /**
13  * Basic argument handler to implement string arguments that may have length
14  * limits.
15  *
16  * @ingroup views_argument_handlers
17  *
18  * @ViewsArgument("string")
19  */
20 class StringArgument extends ArgumentPluginBase {
21
22   /**
23    * {@inheritdoc}
24    */
25   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
26     parent::init($view, $display, $options);
27
28     if (!empty($this->definition['many to one'])) {
29       $this->helper = new ManyToOneHelper($this);
30
31       // Ensure defaults for these, during summaries and stuff:
32       $this->operator = 'or';
33       $this->value = [];
34     }
35   }
36
37   protected function defineOptions() {
38     $options = parent::defineOptions();
39
40     $options['glossary'] = ['default' => FALSE];
41     $options['limit'] = ['default' => 0];
42     $options['case'] = ['default' => 'none'];
43     $options['path_case'] = ['default' => 'none'];
44     $options['transform_dash'] = ['default' => FALSE];
45     $options['break_phrase'] = ['default' => FALSE];
46
47     if (!empty($this->definition['many to one'])) {
48       $options['add_table'] = ['default' => FALSE];
49       $options['require_value'] = ['default' => FALSE];
50     }
51
52     return $options;
53   }
54
55   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
56     parent::buildOptionsForm($form, $form_state);
57
58     $form['glossary'] = [
59       '#type' => 'checkbox',
60       '#title' => $this->t('Glossary mode'),
61       '#description' => $this->t('Glossary mode applies a limit to the number of characters used in the filter value, which allows the summary view to act as a glossary.'),
62       '#default_value' => $this->options['glossary'],
63       '#group' => 'options][more',
64     ];
65
66     $form['limit'] = [
67       '#type' => 'textfield',
68       '#title' => $this->t('Character limit'),
69       '#description' => $this->t('How many characters of the filter value to filter against. If set to 1, all fields starting with the first letter in the filter value would be matched.'),
70       '#default_value' => $this->options['limit'],
71       '#states' => [
72         'visible' => [
73           ':input[name="options[glossary]"]' => ['checked' => TRUE],
74         ],
75       ],
76       '#group' => 'options][more',
77     ];
78
79     $form['case'] = [
80       '#type' => 'select',
81       '#title' => $this->t('Case'),
82       '#description' => $this->t('When printing the title and summary, how to transform the case of the filter value.'),
83       '#options' => [
84         'none' => $this->t('No transform'),
85         'upper' => $this->t('Upper case'),
86         'lower' => $this->t('Lower case'),
87         'ucfirst' => $this->t('Capitalize first letter'),
88         'ucwords' => $this->t('Capitalize each word'),
89       ],
90       '#default_value' => $this->options['case'],
91       '#group' => 'options][more',
92     ];
93
94     $form['path_case'] = [
95       '#type' => 'select',
96       '#title' => $this->t('Case in path'),
97       '#description' => $this->t('When printing URL paths, how to transform the case of the filter value. Do not use this unless with Postgres as it uses case sensitive comparisons.'),
98       '#options' => [
99         'none' => $this->t('No transform'),
100         'upper' => $this->t('Upper case'),
101         'lower' => $this->t('Lower case'),
102         'ucfirst' => $this->t('Capitalize first letter'),
103         'ucwords' => $this->t('Capitalize each word'),
104       ],
105       '#default_value' => $this->options['path_case'],
106       '#group' => 'options][more',
107     ];
108
109     $form['transform_dash'] = [
110       '#type' => 'checkbox',
111       '#title' => $this->t('Transform spaces to dashes in URL'),
112       '#default_value' => $this->options['transform_dash'],
113       '#group' => 'options][more',
114     ];
115
116     if (!empty($this->definition['many to one'])) {
117       $form['add_table'] = [
118         '#type' => 'checkbox',
119         '#title' => $this->t('Allow multiple filter values to work together'),
120         '#description' => $this->t('If selected, multiple instances of this filter can work together, as though multiple values were supplied to the same filter. This setting is not compatible with the "Reduce duplicates" setting.'),
121         '#default_value' => !empty($this->options['add_table']),
122         '#group' => 'options][more',
123       ];
124
125       $form['require_value'] = [
126         '#type' => 'checkbox',
127         '#title' => $this->t('Do not display items with no value in summary'),
128         '#default_value' => !empty($this->options['require_value']),
129         '#group' => 'options][more',
130       ];
131     }
132
133     // allow + for or, , for and
134     $form['break_phrase'] = [
135       '#type' => 'checkbox',
136       '#title' => $this->t('Allow multiple values'),
137       '#description' => $this->t('If selected, users can enter multiple values in the form of 1+2+3 (for OR) or 1,2,3 (for AND).'),
138       '#default_value' => !empty($this->options['break_phrase']),
139       '#group' => 'options][more',
140     ];
141   }
142
143   /**
144    * Build the summary query based on a string
145    */
146   protected function summaryQuery() {
147     if (empty($this->definition['many to one'])) {
148       $this->ensureMyTable();
149     }
150     else {
151       $this->tableAlias = $this->helper->summaryJoin();
152     }
153
154     if (empty($this->options['glossary'])) {
155       // Add the field.
156       $this->base_alias = $this->query->addField($this->tableAlias, $this->realField);
157       $this->query->setCountField($this->tableAlias, $this->realField);
158     }
159     else {
160       // Add the field.
161       $formula = $this->getFormula();
162       $this->base_alias = $this->query->addField(NULL, $formula, $this->field . '_truncated');
163       $this->query->setCountField(NULL, $formula, $this->field, $this->field . '_truncated');
164     }
165
166     $this->summaryNameField();
167     return $this->summaryBasics(FALSE);
168   }
169
170   /**
171    * Get the formula for this argument.
172    *
173    * $this->ensureMyTable() MUST have been called prior to this.
174    */
175   public function getFormula() {
176     $formula = "SUBSTRING($this->tableAlias.$this->realField, 1, " . intval($this->options['limit']) . ")";
177
178     if ($this->options['case'] != 'none') {
179       // Support case-insensitive substring comparisons for SQLite by using the
180       // 'NOCASE_UTF8' collation.
181       // @see Drupal\Core\Database\Driver\sqlite\Connection::open()
182       if (Database::getConnection()->databaseType() == 'sqlite') {
183         $formula .= ' COLLATE NOCASE_UTF8';
184       }
185
186       // Support case-insensitive substring comparisons for PostgreSQL by
187       // converting the formula to lowercase.
188       if (Database::getConnection()->databaseType() == 'pgsql') {
189         $formula = 'LOWER(' . $formula . ')';
190       }
191     }
192     return $formula;
193   }
194
195   /**
196    * Build the query based upon the formula
197    */
198   public function query($group_by = FALSE) {
199     $argument = $this->argument;
200     if (!empty($this->options['transform_dash'])) {
201       $argument = strtr($argument, '-', ' ');
202     }
203
204     if (!empty($this->options['break_phrase'])) {
205       $this->unpackArgumentValue();
206     }
207     else {
208       $this->value = [$argument];
209       $this->operator = 'or';
210     }
211
212     // Support case-insensitive substring comparisons for PostgreSQL by
213     // converting the arguments to lowercase.
214     if ($this->options['case'] != 'none' && Database::getConnection()->databaseType() == 'pgsql') {
215       foreach ($this->value as $key => $value) {
216         $this->value[$key] = mb_strtolower($value);
217       }
218     }
219
220     if (!empty($this->definition['many to one'])) {
221       if (!empty($this->options['glossary'])) {
222         $this->helper->formula = TRUE;
223       }
224       $this->helper->ensureMyTable();
225       $this->helper->addFilter();
226       return;
227     }
228
229     $this->ensureMyTable();
230     $formula = FALSE;
231     if (empty($this->options['glossary'])) {
232       $field = "$this->tableAlias.$this->realField";
233     }
234     else {
235       $formula = TRUE;
236       $field = $this->getFormula();
237     }
238
239     if (count($this->value) > 1) {
240       $operator = 'IN';
241       $argument = $this->value;
242     }
243     else {
244       $operator = '=';
245     }
246
247     if ($formula) {
248       $placeholder = $this->placeholder();
249       if ($operator == 'IN') {
250         $field .= " IN($placeholder)";
251       }
252       else {
253         $field .= ' = ' . $placeholder;
254       }
255       $placeholders = [
256         $placeholder => $argument,
257       ];
258       $this->query->addWhereExpression(0, $field, $placeholders);
259     }
260     else {
261       $this->query->addWhere(0, $field, $argument, $operator);
262     }
263   }
264
265   public function summaryArgument($data) {
266     $value = $this->caseTransform($data->{$this->base_alias}, $this->options['path_case']);
267     if (!empty($this->options['transform_dash'])) {
268       $value = strtr($value, ' ', '-');
269     }
270     return $value;
271   }
272
273   /**
274    * {@inheritdoc}
275    */
276   public function getSortName() {
277     return $this->t('Alphabetical', [], ['context' => 'Sort order']);
278   }
279
280   public function title() {
281     // Support case-insensitive title comparisons for PostgreSQL by converting
282     // the title to lowercase.
283     if ($this->options['case'] != 'none' && Database::getConnection()->databaseType() == 'pgsql') {
284       $this->options['case'] = 'lower';
285     }
286
287     $this->argument = $this->caseTransform($this->argument, $this->options['case']);
288     if (!empty($this->options['transform_dash'])) {
289       $this->argument = strtr($this->argument, '-', ' ');
290     }
291
292     if (!empty($this->options['break_phrase'])) {
293       $this->unpackArgumentValue();
294     }
295     else {
296       $this->value = [$this->argument];
297       $this->operator = 'or';
298     }
299
300     if (empty($this->value)) {
301       return !empty($this->definition['empty field name']) ? $this->definition['empty field name'] : $this->t('Uncategorized');
302     }
303
304     if ($this->value === [-1]) {
305       return !empty($this->definition['invalid input']) ? $this->definition['invalid input'] : $this->t('Invalid input');
306     }
307
308     return implode($this->operator == 'or' ? ' + ' : ', ', $this->titleQuery());
309   }
310
311   /**
312    * Override for specific title lookups.
313    */
314   public function titleQuery() {
315     return $this->value;
316   }
317
318   public function summaryName($data) {
319     return $this->caseTransform(parent::summaryName($data), $this->options['case']);
320   }
321
322   /**
323    * {@inheritdoc}
324    */
325   public function getContextDefinition() {
326     if ($context_definition = parent::getContextDefinition()) {
327       return $context_definition;
328     }
329
330     // If the parent does not provide a context definition through the
331     // validation plugin, fall back to the string type.
332     return new ContextDefinition('string', $this->adminLabel(), FALSE);
333   }
334
335 }