a68c3c8c6cea116782de9916b1e9a028f8109046
[yaffs-website] / web / core / modules / taxonomy / src / Plugin / views / filter / TaxonomyIndexTid.php
1 <?php
2
3 namespace Drupal\taxonomy\Plugin\views\filter;
4
5 use Drupal\Core\Entity\Element\EntityAutocomplete;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\taxonomy\Entity\Term;
8 use Drupal\taxonomy\TermStorageInterface;
9 use Drupal\taxonomy\VocabularyStorageInterface;
10 use Drupal\views\ViewExecutable;
11 use Drupal\views\Plugin\views\display\DisplayPluginBase;
12 use Drupal\views\Plugin\views\filter\ManyToOne;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
14
15 /**
16  * Filter by term id.
17  *
18  * @ingroup views_filter_handlers
19  *
20  * @ViewsFilter("taxonomy_index_tid")
21  */
22 class TaxonomyIndexTid extends ManyToOne {
23
24   // Stores the exposed input for this filter.
25   public $validated_exposed_input = NULL;
26
27   /**
28    * The vocabulary storage.
29    *
30    * @var \Drupal\taxonomy\VocabularyStorageInterface
31    */
32   protected $vocabularyStorage;
33
34   /**
35    * The term storage.
36    *
37    * @var \Drupal\taxonomy\TermStorageInterface
38    */
39   protected $termStorage;
40
41   /**
42    * Constructs a TaxonomyIndexTid object.
43    *
44    * @param array $configuration
45    *   A configuration array containing information about the plugin instance.
46    * @param string $plugin_id
47    *   The plugin_id for the plugin instance.
48    * @param mixed $plugin_definition
49    *   The plugin implementation definition.
50    * @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
51    *   The vocabulary storage.
52    * @param \Drupal\taxonomy\TermStorageInterface $term_storage
53    *   The term storage.
54    */
55   public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage) {
56     parent::__construct($configuration, $plugin_id, $plugin_definition);
57     $this->vocabularyStorage = $vocabulary_storage;
58     $this->termStorage = $term_storage;
59   }
60
61   /**
62    * {@inheritdoc}
63    */
64   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
65     return new static(
66       $configuration,
67       $plugin_id,
68       $plugin_definition,
69       $container->get('entity.manager')->getStorage('taxonomy_vocabulary'),
70       $container->get('entity.manager')->getStorage('taxonomy_term')
71     );
72   }
73
74   /**
75    * {@inheritdoc}
76    */
77   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
78     parent::init($view, $display, $options);
79
80     if (!empty($this->definition['vocabulary'])) {
81       $this->options['vid'] = $this->definition['vocabulary'];
82     }
83   }
84
85   public function hasExtraOptions() { return TRUE; }
86
87   /**
88    * {@inheritdoc}
89    */
90   public function getValueOptions() {
91     return $this->valueOptions;
92   }
93
94   protected function defineOptions() {
95     $options = parent::defineOptions();
96
97     $options['type'] = ['default' => 'textfield'];
98     $options['limit'] = ['default' => TRUE];
99     $options['vid'] = ['default' => ''];
100     $options['hierarchy'] = ['default' => FALSE];
101     $options['error_message'] = ['default' => TRUE];
102
103     return $options;
104   }
105
106   public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
107     $vocabularies = $this->vocabularyStorage->loadMultiple();
108     $options = [];
109     foreach ($vocabularies as $voc) {
110       $options[$voc->id()] = $voc->label();
111     }
112
113     if ($this->options['limit']) {
114       // We only do this when the form is displayed.
115       if (empty($this->options['vid'])) {
116         $first_vocabulary = reset($vocabularies);
117         $this->options['vid'] = $first_vocabulary->id();
118       }
119
120       if (empty($this->definition['vocabulary'])) {
121         $form['vid'] = [
122           '#type' => 'radios',
123           '#title' => $this->t('Vocabulary'),
124           '#options' => $options,
125           '#description' => $this->t('Select which vocabulary to show terms for in the regular options.'),
126           '#default_value' => $this->options['vid'],
127         ];
128       }
129     }
130
131     $form['type'] = [
132       '#type' => 'radios',
133       '#title' => $this->t('Selection type'),
134       '#options' => ['select' => $this->t('Dropdown'), 'textfield' => $this->t('Autocomplete')],
135       '#default_value' => $this->options['type'],
136     ];
137
138     $form['hierarchy'] = [
139       '#type' => 'checkbox',
140       '#title' => $this->t('Show hierarchy in dropdown'),
141       '#default_value' => !empty($this->options['hierarchy']),
142       '#states' => [
143         'visible' => [
144           ':input[name="options[type]"]' => ['value' => 'select'],
145         ],
146       ],
147     ];
148   }
149
150   protected function valueForm(&$form, FormStateInterface $form_state) {
151     $vocabulary = $this->vocabularyStorage->load($this->options['vid']);
152     if (empty($vocabulary) && $this->options['limit']) {
153       $form['markup'] = [
154         '#markup' => '<div class="js-form-item form-item">' . $this->t('An invalid vocabulary is selected. Please change it in the options.') . '</div>',
155       ];
156       return;
157     }
158
159     if ($this->options['type'] == 'textfield') {
160       $terms = $this->value ? Term::loadMultiple(($this->value)) : [];
161       $form['value'] = [
162         '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
163         '#type' => 'textfield',
164         '#default_value' => EntityAutocomplete::getEntityLabels($terms),
165       ];
166
167       if ($this->options['limit']) {
168         $form['value']['#type'] = 'entity_autocomplete';
169         $form['value']['#target_type'] = 'taxonomy_term';
170         $form['value']['#selection_settings']['target_bundles'] = [$vocabulary->id()];
171         $form['value']['#tags'] = TRUE;
172         $form['value']['#process_default_value'] = FALSE;
173       }
174     }
175     else {
176       if (!empty($this->options['hierarchy']) && $this->options['limit']) {
177         $tree = $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE);
178         $options = [];
179
180         if ($tree) {
181           foreach ($tree as $term) {
182             $choice = new \stdClass();
183             $choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label()];
184             $options[] = $choice;
185           }
186         }
187       }
188       else {
189         $options = [];
190         $query = \Drupal::entityQuery('taxonomy_term')
191           // @todo Sorting on vocabulary properties -
192           //   https://www.drupal.org/node/1821274.
193           ->sort('weight')
194           ->sort('name')
195           ->addTag('taxonomy_term_access');
196         if ($this->options['limit']) {
197           $query->condition('vid', $vocabulary->id());
198         }
199         $terms = Term::loadMultiple($query->execute());
200         foreach ($terms as $term) {
201           $options[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
202         }
203       }
204
205       $default_value = (array) $this->value;
206
207       if ($exposed = $form_state->get('exposed')) {
208         $identifier = $this->options['expose']['identifier'];
209
210         if (!empty($this->options['expose']['reduce'])) {
211           $options = $this->reduceValueOptions($options);
212
213           if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
214             $default_value = [];
215           }
216         }
217
218         if (empty($this->options['expose']['multiple'])) {
219           if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
220             $default_value = 'All';
221           }
222           elseif (empty($default_value)) {
223             $keys = array_keys($options);
224             $default_value = array_shift($keys);
225           }
226           // Due to #1464174 there is a chance that array('') was saved in the admin ui.
227           // Let's choose a safe default value.
228           elseif ($default_value == ['']) {
229             $default_value = 'All';
230           }
231           else {
232             $copy = $default_value;
233             $default_value = array_shift($copy);
234           }
235         }
236       }
237       $form['value'] = [
238         '#type' => 'select',
239         '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
240         '#multiple' => TRUE,
241         '#options' => $options,
242         '#size' => min(9, count($options)),
243         '#default_value' => $default_value,
244       ];
245
246       $user_input = $form_state->getUserInput();
247       if ($exposed && isset($identifier) && !isset($user_input[$identifier])) {
248         $user_input[$identifier] = $default_value;
249         $form_state->setUserInput($user_input);
250       }
251     }
252
253     if (!$form_state->get('exposed')) {
254       // Retain the helper option
255       $this->helper->buildOptionsForm($form, $form_state);
256
257       // Show help text if not exposed to end users.
258       $form['value']['#description'] = t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".');
259     }
260   }
261
262   protected function valueValidate($form, FormStateInterface $form_state) {
263     // We only validate if they've chosen the text field style.
264     if ($this->options['type'] != 'textfield') {
265       return;
266     }
267
268     $tids = [];
269     if ($values = $form_state->getValue(['options', 'value'])) {
270       foreach ($values as $value) {
271         $tids[] = $value['target_id'];
272       }
273     }
274     $form_state->setValue(['options', 'value'], $tids);
275   }
276
277   public function acceptExposedInput($input) {
278     if (empty($this->options['exposed'])) {
279       return TRUE;
280     }
281     // We need to know the operator, which is normally set in
282     // \Drupal\views\Plugin\views\filter\FilterPluginBase::acceptExposedInput(),
283     // before we actually call the parent version of ourselves.
284     if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
285       $this->operator = $input[$this->options['expose']['operator_id']];
286     }
287
288     // If view is an attachment and is inheriting exposed filters, then assume
289     // exposed input has already been validated
290     if (!empty($this->view->is_attachment) && $this->view->display_handler->usesExposed()) {
291       $this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
292     }
293
294     // If we're checking for EMPTY or NOT, we don't need any input, and we can
295     // say that our input conditions are met by just having the right operator.
296     if ($this->operator == 'empty' || $this->operator == 'not empty') {
297       return TRUE;
298     }
299
300     // If it's non-required and there's no value don't bother filtering.
301     if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
302       return FALSE;
303     }
304
305     $rc = parent::acceptExposedInput($input);
306     if ($rc) {
307       // If we have previously validated input, override.
308       if (isset($this->validated_exposed_input)) {
309         $this->value = $this->validated_exposed_input;
310       }
311     }
312
313     return $rc;
314   }
315
316   public function validateExposed(&$form, FormStateInterface $form_state) {
317     if (empty($this->options['exposed'])) {
318       return;
319     }
320
321     $identifier = $this->options['expose']['identifier'];
322
323     // We only validate if they've chosen the text field style.
324     if ($this->options['type'] != 'textfield') {
325       if ($form_state->getValue($identifier) != 'All') {
326         $this->validated_exposed_input = (array) $form_state->getValue($identifier);
327       }
328       return;
329     }
330
331     if (empty($this->options['expose']['identifier'])) {
332       return;
333     }
334
335     if ($values = $form_state->getValue($identifier)) {
336       foreach ($values as $value) {
337         $this->validated_exposed_input[] = $value['target_id'];
338       }
339     }
340   }
341
342   protected function valueSubmit($form, FormStateInterface $form_state) {
343     // prevent array_filter from messing up our arrays in parent submit.
344   }
345
346   public function buildExposeForm(&$form, FormStateInterface $form_state) {
347     parent::buildExposeForm($form, $form_state);
348     if ($this->options['type'] != 'select') {
349       unset($form['expose']['reduce']);
350     }
351     $form['error_message'] = [
352       '#type' => 'checkbox',
353       '#title' => $this->t('Display error message'),
354       '#default_value' => !empty($this->options['error_message']),
355     ];
356   }
357
358   public function adminSummary() {
359     // set up $this->valueOptions for the parent summary
360     $this->valueOptions = [];
361
362     if ($this->value) {
363       $this->value = array_filter($this->value);
364       $terms = Term::loadMultiple($this->value);
365       foreach ($terms as $term) {
366         $this->valueOptions[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
367       }
368     }
369     return parent::adminSummary();
370   }
371
372   /**
373    * {@inheritdoc}
374    */
375   public function getCacheContexts() {
376     $contexts = parent::getCacheContexts();
377     // The result potentially depends on term access and so is just cacheable
378     // per user.
379     // @todo See https://www.drupal.org/node/2352175.
380     $contexts[] = 'user';
381
382     return $contexts;
383   }
384
385   /**
386    * {@inheritdoc}
387    */
388   public function calculateDependencies() {
389     $dependencies = parent::calculateDependencies();
390
391     $vocabulary = $this->vocabularyStorage->load($this->options['vid']);
392     $dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
393
394     foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) {
395       $dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
396     }
397
398     return $dependencies;
399   }
400
401 }