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