vocabularyStorage = $vocabulary_storage; $this->termStorage = $term_storage; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('entity.manager')->getStorage('taxonomy_vocabulary'), $container->get('entity.manager')->getStorage('taxonomy_term') ); } /** * {@inheritdoc} */ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { parent::init($view, $display, $options); if (!empty($this->definition['vocabulary'])) { $this->options['vid'] = $this->definition['vocabulary']; } } public function hasExtraOptions() { return TRUE; } /** * {@inheritdoc} */ public function getValueOptions() { return $this->valueOptions; } protected function defineOptions() { $options = parent::defineOptions(); $options['type'] = ['default' => 'textfield']; $options['limit'] = ['default' => TRUE]; $options['vid'] = ['default' => '']; $options['hierarchy'] = ['default' => FALSE]; $options['error_message'] = ['default' => TRUE]; return $options; } public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) { $vocabularies = $this->vocabularyStorage->loadMultiple(); $options = []; foreach ($vocabularies as $voc) { $options[$voc->id()] = $voc->label(); } if ($this->options['limit']) { // We only do this when the form is displayed. if (empty($this->options['vid'])) { $first_vocabulary = reset($vocabularies); $this->options['vid'] = $first_vocabulary->id(); } if (empty($this->definition['vocabulary'])) { $form['vid'] = [ '#type' => 'radios', '#title' => $this->t('Vocabulary'), '#options' => $options, '#description' => $this->t('Select which vocabulary to show terms for in the regular options.'), '#default_value' => $this->options['vid'], ]; } } $form['type'] = [ '#type' => 'radios', '#title' => $this->t('Selection type'), '#options' => ['select' => $this->t('Dropdown'), 'textfield' => $this->t('Autocomplete')], '#default_value' => $this->options['type'], ]; $form['hierarchy'] = [ '#type' => 'checkbox', '#title' => $this->t('Show hierarchy in dropdown'), '#default_value' => !empty($this->options['hierarchy']), '#states' => [ 'visible' => [ ':input[name="options[type]"]' => ['value' => 'select'], ], ], ]; } protected function valueForm(&$form, FormStateInterface $form_state) { $vocabulary = $this->vocabularyStorage->load($this->options['vid']); if (empty($vocabulary) && $this->options['limit']) { $form['markup'] = [ '#markup' => '
' . $this->t('An invalid vocabulary is selected. Please change it in the options.') . '
', ]; return; } if ($this->options['type'] == 'textfield') { $terms = $this->value ? Term::loadMultiple(($this->value)) : []; $form['value'] = [ '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'), '#type' => 'textfield', '#default_value' => EntityAutocomplete::getEntityLabels($terms), ]; if ($this->options['limit']) { $form['value']['#type'] = 'entity_autocomplete'; $form['value']['#target_type'] = 'taxonomy_term'; $form['value']['#selection_settings']['target_bundles'] = [$vocabulary->id()]; $form['value']['#tags'] = TRUE; $form['value']['#process_default_value'] = FALSE; } } else { if (!empty($this->options['hierarchy']) && $this->options['limit']) { $tree = $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE); $options = []; if ($tree) { foreach ($tree as $term) { $choice = new \stdClass(); $choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label()]; $options[] = $choice; } } } else { $options = []; $query = \Drupal::entityQuery('taxonomy_term') // @todo Sorting on vocabulary properties - // https://www.drupal.org/node/1821274. ->sort('weight') ->sort('name') ->addTag('taxonomy_term_access'); if ($this->options['limit']) { $query->condition('vid', $vocabulary->id()); } $terms = Term::loadMultiple($query->execute()); foreach ($terms as $term) { $options[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label(); } } $default_value = (array) $this->value; if ($exposed = $form_state->get('exposed')) { $identifier = $this->options['expose']['identifier']; if (!empty($this->options['expose']['reduce'])) { $options = $this->reduceValueOptions($options); 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']))) { $default_value = 'All'; } elseif (empty($default_value)) { $keys = array_keys($options); $default_value = array_shift($keys); } // Due to #1464174 there is a chance that array('') was saved in the admin ui. // Let's choose a safe default value. elseif ($default_value == ['']) { $default_value = 'All'; } else { $copy = $default_value; $default_value = array_shift($copy); } } } $form['value'] = [ '#type' => 'select', '#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'), '#multiple' => TRUE, '#options' => $options, '#size' => min(9, count($options)), '#default_value' => $default_value, ]; $user_input = $form_state->getUserInput(); if ($exposed && isset($identifier) && !isset($user_input[$identifier])) { $user_input[$identifier] = $default_value; $form_state->setUserInput($user_input); } } if (!$form_state->get('exposed')) { // Retain the helper option $this->helper->buildOptionsForm($form, $form_state); // Show help text if not exposed to end users. $form['value']['#description'] = t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".'); } } protected function valueValidate($form, FormStateInterface $form_state) { // We only validate if they've chosen the text field style. if ($this->options['type'] != 'textfield') { return; } $tids = []; if ($values = $form_state->getValue(['options', 'value'])) { foreach ($values as $value) { $tids[] = $value['target_id']; } } $form_state->setValue(['options', 'value'], $tids); } public function acceptExposedInput($input) { if (empty($this->options['exposed'])) { return TRUE; } // We need to know the operator, which is normally set in // \Drupal\views\Plugin\views\filter\FilterPluginBase::acceptExposedInput(), // before we actually call the parent version of ourselves. if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) { $this->operator = $input[$this->options['expose']['operator_id']]; } // If view is an attachment and is inheriting exposed filters, then assume // exposed input has already been validated if (!empty($this->view->is_attachment) && $this->view->display_handler->usesExposed()) { $this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']]; } // If we're checking for EMPTY or NOT, we don't need any input, and we can // say that our input conditions are met by just having the right operator. if ($this->operator == 'empty' || $this->operator == 'not empty') { return TRUE; } // If it's non-required and there's no value don't bother filtering. if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) { return FALSE; } $rc = parent::acceptExposedInput($input); if ($rc) { // If we have previously validated input, override. if (isset($this->validated_exposed_input)) { $this->value = $this->validated_exposed_input; } } return $rc; } public function validateExposed(&$form, FormStateInterface $form_state) { if (empty($this->options['exposed'])) { return; } $identifier = $this->options['expose']['identifier']; // We only validate if they've chosen the text field style. if ($this->options['type'] != 'textfield') { if ($form_state->getValue($identifier) != 'All') { $this->validated_exposed_input = (array) $form_state->getValue($identifier); } return; } if (empty($this->options['expose']['identifier'])) { return; } if ($values = $form_state->getValue($identifier)) { foreach ($values as $value) { $this->validated_exposed_input[] = $value['target_id']; } } } protected function valueSubmit($form, FormStateInterface $form_state) { // prevent array_filter from messing up our arrays in parent submit. } public function buildExposeForm(&$form, FormStateInterface $form_state) { parent::buildExposeForm($form, $form_state); if ($this->options['type'] != 'select') { unset($form['expose']['reduce']); } $form['error_message'] = [ '#type' => 'checkbox', '#title' => $this->t('Display error message'), '#default_value' => !empty($this->options['error_message']), ]; } public function adminSummary() { // set up $this->valueOptions for the parent summary $this->valueOptions = []; if ($this->value) { $this->value = array_filter($this->value); $terms = Term::loadMultiple($this->value); foreach ($terms as $term) { $this->valueOptions[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label(); } } return parent::adminSummary(); } /** * {@inheritdoc} */ public function getCacheContexts() { $contexts = parent::getCacheContexts(); // The result potentially depends on term access and so is just cacheable // per user. // @todo See https://www.drupal.org/node/2352175. $contexts[] = 'user'; return $contexts; } /** * {@inheritdoc} */ public function calculateDependencies() { $dependencies = parent::calculateDependencies(); $vocabulary = $this->vocabularyStorage->load($this->options['vid']); $dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName(); foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) { $dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName(); } return $dependencies; } }