diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/Term.php index 7491199..56a4701 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/Term.php @@ -9,20 +9,12 @@ use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; -use Drupal\Component\Annotation\Plugin; -use Drupal\Core\Annotation\Translation; -use Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase; +use Drupal\views\Plugin\views\argument_validator\Entity; /** - * Validate whether an argument is an acceptable node. - * - * @Plugin( - * id = "taxonomy_term", - * module = "taxonomy", - * title = @Translation("Taxonomy term") - * ) + * Adds legacy vocabulary handling to standard Entity Argument validation.. */ -class Term extends ArgumentValidatorPluginBase { +class Term extends Entity { /** * Overrides \Drupal\views\Plugin\views\Plugin\views\PluginBase::init(). @@ -41,166 +33,4 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o } } } - - protected function defineOptions() { - $options = parent::defineOptions(); - $options['vids'] = array('default' => array()); - $options['type'] = array('default' => 'tid'); - $options['transform'] = array('default' => FALSE, 'bool' => TRUE); - - return $options; - } - - public function buildOptionsForm(&$form, &$form_state) { - $vocabularies = entity_load_multiple('taxonomy_vocabulary'); - $options = array(); - foreach ($vocabularies as $voc) { - $options[$voc->id()] = $voc->label(); - } - - $form['vids'] = array( - '#type' => 'checkboxes', - '#prefix' => '
', - '#suffix' => '
', - '#title' => t('Vocabularies'), - '#options' => $options, - '#default_value' => $this->options['vids'], - '#description' => t('If you wish to validate for specific vocabularies, check them; if none are checked, all terms will pass.'), - ); - - $form['type'] = array( - '#type' => 'select', - '#title' => t('Filter value type'), - '#options' => array( - 'tid' => t('Term ID'), - 'tids' => t('Term IDs separated by , or +'), - 'name' => t('Term name'), - 'convert' => t('Term name converted to Term ID'), - ), - '#default_value' => $this->options['type'], - '#description' => t('Select the form of this filter value; if using term name, it is generally more efficient to convert it to a term ID and use Taxonomy: Term ID rather than Taxonomy: Term Name" as the filter.'), - ); - - $form['transform'] = array( - '#type' => 'checkbox', - '#title' => t('Transform dashes in URL to spaces in term name filter values'), - '#default_value' => $this->options['transform'], - ); - } - - public function submitOptionsForm(&$form, &$form_state, &$options = array()) { - // Filter unselected items so we don't unnecessarily store giant arrays. - $options['vids'] = array_filter($options['vids']); - } - - public function validateArgument($argument) { - $vocabularies = array_filter($this->options['vids']); - $type = $this->options['type']; - $transform = $this->options['transform']; - - switch ($type) { - case 'tid': - if (!is_numeric($argument)) { - return FALSE; - } - // @todo Deal with missing addTag('term access') that was removed when - // the db_select that was replaced by the entity_load. - $term = entity_load('taxonomy_term', $argument); - if (!$term) { - return FALSE; - } - $this->argument->validated_title = check_plain($term->label()); - return empty($vocabularies) || !empty($vocabularies[$term->bundle()]); - - case 'tids': - // An empty argument is not a term so doesn't pass. - if (empty($argument)) { - return FALSE; - } - - $tids = new stdClass(); - $tids->value = $argument; - $tids = $this->breakPhrase($argument, $tids); - if ($tids->value == array(-1)) { - return FALSE; - } - - $test = drupal_map_assoc($tids->value); - $titles = array(); - - // check, if some tids already verified - static $validated_cache = array(); - foreach ($test as $tid) { - if (isset($validated_cache[$tid])) { - if ($validated_cache[$tid] === FALSE) { - return FALSE; - } - else { - $titles[] = $validated_cache[$tid]; - unset($test[$tid]); - } - } - } - - // if unverified tids left - verify them and cache results - if (count($test)) { - $result = entity_load_multiple('taxonomy_term', $test); - foreach ($result as $term) { - if ($vocabularies && empty($vocabularies[$term->bundle()])) { - $validated_cache[$term->id()] = FALSE; - return FALSE; - } - - $titles[] = $validated_cache[$term->id()] = check_plain($term->label()); - unset($test[$term->id()]); - } - } - - // Remove duplicate titles - $titles = array_unique($titles); - - $this->argument->validated_title = implode($tids->operator == 'or' ? ' + ' : ', ', $titles); - // If this is not empty, we did not find a tid. - return empty($test); - - case 'name': - case 'convert': - $terms = entity_load_multiple_by_properties('taxonomy_term', array('name' => $argument)); - $term = reset($terms); - if ($transform) { - $term->name = str_replace(' ', '-', $term->name); - } - - if ($term && (empty($vocabularies) || !empty($vocabularies[$term->bundle()]))) { - if ($type == 'convert') { - $this->argument->argument = $term->id(); - } - $this->argument->validated_title = check_plain($term->label()); - return TRUE; - } - return FALSE; - } - } - - public function processSummaryArguments(&$args) { - $type = $this->options['type']; - $transform = $this->options['transform']; - - if ($type == 'convert') { - $arg_keys = array_flip($args); - - $result = entity_load_multiple('taxonomy_term', $args); - - if ($transform) { - foreach ($result as $term) { - $term->name = str_replace(' ', '-', $term->name); - } - } - - foreach ($result as $tid => $term) { - $args[$arg_keys[$tid]] = $term; - } - } - } - } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php new file mode 100644 index 0000000..29462ed --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php @@ -0,0 +1,105 @@ +multipleCapable = FALSE; + } + + + /** + * Overrides \Drupal\views\Plugin\views\Plugin\views\PluginBase::init(). + */ + public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { + parent::init($view, $display, $options); + + // @todo Remove the legacy code. + // Convert legacy vids option to machine name vocabularies. + if (!empty($this->options['vids'])) { + $vocabularies = taxonomy_vocabulary_get_names(); + foreach ($this->options['vids'] as $vid) { + if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) { + $this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name; + } + } + } + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['transform'] = array('default' => FALSE, 'bool' => TRUE); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($form, $form_state); + + // @todo CAN we do that here now? + $form['transform'] = array( + '#type' => 'checkbox', + '#title' => t('Transform dashes in URL to spaces in term name filter values'), + '#default_value' => $this->options['transform'], + ); + } + + /** + * {@inheritdoc} + */ + public function validateArgument($argument) { + if ($this->options['transform']) { + $argument = str_replace('-', ' ', $argument); + } + $terms = entity_load_multiple_by_properties('taxonomy_term', array('name' => $argument)); + + if (! count($terms)) { + // Returned empty array no terms with the name. + return FALSE; + } + + // Not knowing which term will be used if more than one is returned check + // each one. + foreach ($terms as $term) { + if (! $this->validateEntity($term)) { + return FALSE; + } + } + + return TRUE; + } +} diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc index 5646268..eb161ba 100644 --- a/core/modules/taxonomy/taxonomy.views.inc +++ b/core/modules/taxonomy/taxonomy.views.inc @@ -425,3 +425,16 @@ function views_taxonomy_set_breadcrumb(&$breadcrumb, &$argument) { $breadcrumb[$path] = check_plain($parent->label()); } } + +/** + * Implements hook_views_plugins_argument_validator_alter(). + * + * Extend the generic entity argument validator. + * + * @see \Drupal\views\Plugin\views\argument_validator\Entity + */ +function taxonomy_views_plugins_argument_validator_alter(array &$plugins) { + $plugins['entity:taxonomy_term']['title'] = t('Taxonomy term ID'); + $plugins['entity:taxonomy_term']['class'] = 'Drupal\taxonomy\Plugin\views\argument_validator\Term'; + $plugins['entity:taxonomy_term']['module'] = 'taxonomy'; +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php new file mode 100644 index 0000000..48eb9a9 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php @@ -0,0 +1,57 @@ +derivatives) && !empty($this->derivatives[$derivative_id])) { + return $this->derivatives[$derivative_id]; + } + $this->getDerivativeDefinitions($base_plugin_definition); + return $this->derivatives[$derivative_id]; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions(array $base_plugin_definition) { + $entity_info = \Drupal::entityManager()->getDefinitions(); + foreach ($entity_info as $entity_type => $entity_info) { + $this->derivatives[$entity_type] = array( + 'id' => 'entity:' . $entity_type, + 'module' => 'views', + 'title' => $entity_info['label'], + 'help' => t('Validate @label', array('@label' => $entity_info['label'])), + 'entity_type' => $entity_type, + 'class' => $base_plugin_definition['class'], + ); + } + + return $this->derivatives; + } +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/argument/ArgumentPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/argument/ArgumentPluginBase.php index 862a847..e33aa2d 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/argument/ArgumentPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/argument/ArgumentPluginBase.php @@ -294,7 +294,7 @@ public function buildOptionsForm(&$form, &$form_state) { $form['validate']['type'] = array( '#type' => 'select', '#title' => t('Validator'), - '#default_value' => $this->options['validate']['type'], + '#default_value' => str_replace(':', '-', $this->options['validate']['type']), '#states' => array( 'visible' => array( ':input[name="options[specify_validation]"]' => array('checked' => TRUE), @@ -327,8 +327,9 @@ public function buildOptionsForm(&$form, &$form_state) { $plugin = $this->getPlugin('argument_validator', $id); if ($plugin) { if ($plugin->access() || $this->options['validate']['type'] == $id) { - $form['validate']['options'][$id] = array( - '#prefix' => '
', + $sanitized_id = str_replace(':', '-', $id); + $form['validate']['options'][$sanitized_id] = array( + '#prefix' => '
', '#suffix' => '
', '#type' => 'item', // Even if the plugin has no options add the key to the form_state. @@ -336,14 +337,14 @@ public function buildOptionsForm(&$form, &$form_state) { '#states' => array( 'visible' => array( ':input[name="options[specify_validation]"]' => array('checked' => TRUE), - ':input[name="options[validate][type]"]' => array('value' => $id), + ':input[name="options[validate][type]"]' => array('value' => $sanitized_id), ), ), - '#id' => 'edit-options-validate-options-' . $id, + '#id' => 'edit-options-validate-options-' . $sanitized_id, '#default_value' => array(), ); - $plugin->buildOptionsForm($form['validate']['options'][$id], $form_state); - $validate_types[$id] = $info['title']; + $plugin->buildOptionsForm($form['validate']['options'][$sanitized_id], $form_state); + $validate_types[$sanitized_id] = $info['title']; } } } @@ -385,10 +386,12 @@ public function validateOptionsForm(&$form, &$form_state) { $plugin->validateOptionsForm($form['summary']['options'][$summary_id], $form_state, $form_state['values']['options']['summary']['options'][$summary_id]); } - $validate_id = $form_state['values']['options']['validate']['type']; + $sanitized_id = $form_state['values']['options']['validate']['type']; + // Correct ID for js sanitized version. + $validate_id = str_replace('-', ':', $sanitized_id); $plugin = $this->getPlugin('argument_validator', $validate_id); if ($plugin) { - $plugin->validateOptionsForm($form['validate']['options'][$default_id], $form_state, $form_state['values']['options']['validate']['options'][$validate_id]); + $plugin->validateOptionsForm($form['validate']['options'][$default_id], $form_state, $form_state['values']['options']['validate']['options'][$sanitized_id]); } } @@ -418,11 +421,13 @@ public function submitOptionsForm(&$form, &$form_state) { $form_state['values']['options']['summary_options'] = $options; } - $validate_id = $form_state['values']['options']['validate']['type']; + $sanitized_id = $form_state['values']['options']['validate']['type']; + // Correct ID for js sanitized version. + $form_state['values']['options']['validate']['type'] = $validate_id = str_replace('-', ':', $sanitized_id); $plugin = $this->getPlugin('argument_validator', $validate_id); if ($plugin) { - $options = &$form_state['values']['options']['validate']['options'][$validate_id]; - $plugin->submitOptionsForm($form['validate']['options'][$validate_id], $form_state, $options); + $options = &$form_state['values']['options']['validate']['options'][$sanitized_id]; + $plugin->submitOptionsForm($form['validate']['options'][$sanitized_id], $form_state, $options); // Copy the now submitted options to their final resting place so they get saved. $form_state['values']['options']['validate_options'] = $options; } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php b/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php new file mode 100644 index 0000000..c087ec7 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php @@ -0,0 +1,204 @@ +entityManager = $entity_manager; + $this->multipleCapable = TRUE; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.entity') + ); + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + + $options['bundles'] = array('default' => array()); + $options['access'] = array('default' => FALSE, 'bool' => TRUE); + $options['operation'] = array('default' => 'view'); + $options['multiple'] = array('default' => FALSE, 'bool' => TRUE); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($form, $form_state); + + $entity_type = $this->definition['entity_type']; + // Derivative id's are all entity:entity_type and converted back on + // submission. Other child classes can use another id. + $sanitized_id = str_replace(':', '-', $this->definition['id']); + $entity_definitions = $this->entityManager->getDefinitions(); + $bundle_type = $entity_definitions[$entity_type]['entity_keys']['bundle']; + + // If the entity has bundles, allow option to restrict to bundle(s). + if ($bundle_type) { + $bundles = entity_get_bundles($entity_type); + $bundle_options = array(); + foreach ($bundles as $bundle_id => $bundle_info) { + $bundle_options[$bundle_id] = $bundle_info['label']; + } + $storage_controller = $this->entityManager->getStorageController($entity_type); + $fields = $storage_controller->baseFieldDefinitions(); + $form['bundles'] = array( + '#title' => $entity_definitions[$entity_type]['bundle_label'], + '#default_value' => $this->options['bundles'], + '#type' => 'checkboxes', + '#options' => $bundle_options, + '#description' => t('Restrict to one or more %bundle_name. If none selected all are allowed.', array('%bundle_name' => $fields[$bundle_type]['label'])), + ); + } + + // Offer the option to filter by access to the entity in the argument. + $form['access'] = array( + '#type' => 'checkbox', + '#title' => t('Validate user has access to the %name', array('%name' => $entity_definitions[$entity_type]['label'])), + '#default_value' => $this->options['access'], + ); + $form['operation'] = array( + '#type' => 'radios', + '#title' => t('Access operation to check'), + '#options' => array('view' => t('View'), 'update' => t('Edit'), 'delete' => t('Delete')), + '#default_value' => $this->options['operation'], + '#states' => array( + 'visible' => array( + ':input[name="options[validate][options][' . $sanitized_id . '][access]"]' => array('checked' => TRUE), + ), + ), + ); + + // If class is multiple capable give the option to validate single/multiple. + if ($this->multipleCapable) { + $form['multiple'] = array( + '#type' => 'radios', + '#title' => t('Multiple arguments'), + '#options' => array( + 0 => t('Single ID', array('%type' => $entity_definitions[$entity_type]['label'])), + 1 => t('One or more IDs separated by , or +', array('%type' => $entity_definitions[$entity_type]['label'])), + ), + '#default_value' => (string) $this->options['multiple'], + ); + } + } + + /** + * {@inheritdoc} + */ + public function submitOptionsForm(&$form, &$form_state, &$options = array()) { + // Filter out unused options so we don't store giant unnecessary arrays. + $options['bundles'] = array_filter($options['bundles']); + } + + /** + * {@inheritdoc} + */ + public function validateArgument($argument) { + $entity_type = $this->definition['entity_type']; + + if ($this->options['multiple']) { + // At this point only interested in individual IDs no matter what type, + // just splitting by the allowed delimiters. + $ids = array_filter(preg_split('/[,+ ]/', $argument)); + } + else { + $ids = array($argument); + } + + $entities = entity_load_multiple($entity_type, $ids); + // Validate each id => entity. If any fails break out and return false. + foreach ($ids as $id) { + // There is no entity for this ID. + if (! isset($entities[$id])) { + return FALSE; + } + if (! $this->validateEntity($entities[$id])) { + return FALSE; + } + } + + return TRUE; + } + + /** + * Validate an individual entity against class access settings. + * + * @param Entity $entity + * + * @return bool + * True if validated. + */ + protected function validateEntity(\Drupal\Core\Entity\Entity $entity) { + // If access restricted by entity operation. + if ($this->options['access'] && ! $entity->access($this->options['operation'])) { + return FALSE; + } + // If restricted by bundle. + $bundles = $this->options['bundles']; + if (count($bundles) && empty($bundles[$entity->bundle()])) { + return FALSE; + } + + return TRUE; + } +}