Commit 4e9a8851 authored by catch's avatar catch
Browse files

Issue #2204239 by damiankloip, Sutharsan: Simplify and de-duplicate argument validators.

parent 1c22cf66
<?php
/**
* @file
* Definition of Drupal\node\Plugin\views\argument_validator\Node.
*/
namespace Drupal\node\Plugin\views\argument_validator;
use Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase;
/**
* Validate whether an argument is an acceptable node.
*
* @ViewsArgumentValidator(
* id = "node",
* title = @Translation("Content")
* )
*/
class Node extends ArgumentValidatorPluginBase {
protected function defineOptions() {
$options = parent::defineOptions();
$options['types'] = array('default' => array());
$options['access'] = array('default' => FALSE, 'bool' => TRUE);
$options['access_op'] = array('default' => 'view');
$options['nid_type'] = array('default' => 'nid');
return $options;
}
public function buildOptionsForm(&$form, &$form_state) {
$types = node_type_get_types();
$options = array();
foreach ($types as $type => $info) {
$options[$type] = check_plain(t($info->name));
}
$form['types'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types'),
'#options' => $options,
'#default_value' => $this->options['types'],
'#description' => t('Choose one or more content types to validate with.'),
);
$form['access'] = array(
'#type' => 'checkbox',
'#title' => t('Validate user has access to the content'),
'#default_value' => $this->options['access'],
);
$form['access_op'] = array(
'#type' => 'radios',
'#title' => t('Access operation to check'),
'#options' => array('view' => t('View'), 'update' => t('Edit'), 'delete' => t('Delete')),
'#default_value' => $this->options['access_op'],
'#states' => array(
'visible' => array(
':input[name="options[validate][options][node][access]"]' => array('checked' => TRUE),
),
),
);
$form['nid_type'] = array(
'#type' => 'select',
'#title' => t('Filter value format'),
'#options' => array(
'nid' => t('Node ID'),
'nids' => t('Node IDs separated by , or +'),
),
'#default_value' => $this->options['nid_type'],
);
}
public function submitOptionsForm(&$form, &$form_state, &$options = array()) {
// filter trash out of the options so we don't store giant unnecessary arrays
$options['types'] = array_filter($options['types']);
}
public function validateArgument($argument) {
$types = $this->options['types'];
switch ($this->options['nid_type']) {
case 'nid':
if (!is_numeric($argument)) {
return FALSE;
}
$node = node_load($argument);
if (!$node) {
return FALSE;
}
if (!empty($this->options['access'])) {
if (!$node->access($this->options['access_op'])) {
return FALSE;
}
}
// Save the title() handlers some work.
$this->argument->validated_title = check_plain($node->label());
if (empty($types)) {
return TRUE;
}
return isset($types[$node->getType()]);
case 'nids':
$nids = new stdClass();
$nids->value = array($argument);
$nids = $this->breakPhrase($argument, $nids);
if ($nids->value == array(-1)) {
return FALSE;
}
$test = array_combine($nids->value, $nids->value);
$titles = array();
$nodes = node_load_multiple($nids->value);
foreach ($nodes as $node) {
if ($types && empty($types[$node->getType()])) {
return FALSE;
}
if (!empty($this->options['access'])) {
if (!$node->access($this->options['access_op'])) {
return FALSE;
}
}
$titles[] = check_plain($node->label());
unset($test[$node->id()]);
}
$this->argument->validated_title = implode($nids->operator == 'or' ? ' + ' : ', ', $titles);
// If this is not empty, we did not find a nid.
return empty($test);
}
}
}
......@@ -7,9 +7,9 @@
namespace Drupal\user\Plugin\views\argument_validator;
use Drupal\Core\Database\Connection;
use Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\views\Plugin\views\argument_validator\Entity;
/**
* Validate whether an argument is a valid user.
......@@ -17,79 +17,54 @@
* This supports either numeric arguments (UID) or strings (username) and
* converts either one into the user's UID. This validator also sets the
* argument's title to the username.
*
* @ViewsArgumentValidator(
* id = "user",
* title = @Translation("User")
* )
*/
class User extends ArgumentValidatorPluginBase {
class User extends Entity {
/**
* Database Service Object.
* The user storage controller.
*
* @var \Drupal\Core\Database\Connection
* @var \Drupal\Core\Entity\EntityStorageControllerInterface
*/
protected $database;
protected $userStorage;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Database\Connection $database
* Database Service Object.
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
$this->database = $database;
$this->userStorage = $entity_manager->getStorageController('user');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'));
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['type'] = array('default' => 'uid');
$options['restrict_roles'] = array('default' => FALSE, 'bool' => TRUE);
$options['roles'] = array('default' => array());
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, &$form_state) {
$form['type'] = array(
'#type' => 'radios',
'#title' => t('Type of user filter value to allow'),
'#options' => array(
'uid' => t('Only allow numeric UIDs'),
'name' => t('Only allow string usernames'),
'either' => t('Allow both numeric UIDs and string usernames'),
),
'#default_value' => $this->options['type'],
);
parent::buildOptionsForm($form, $form_state);
$form['restrict_roles'] = array(
'#type' => 'checkbox',
'#title' => t('Restrict user based on role'),
'#title' => $this->t('Restrict user based on role'),
'#default_value' => $this->options['restrict_roles'],
);
$form['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Restrict to the selected roles'),
'#title' => $this->t('Restrict to the selected roles'),
'#options' => array_map('check_plain', user_role_names(TRUE)),
'#default_value' => $this->options['roles'],
'#description' => t('If no roles are selected, users from any role will be allowed.'),
'#description' => $this->t('If no roles are selected, users from any role will be allowed.'),
'#states' => array(
'visible' => array(
':input[name="options[validate][options][user][restrict_roles]"]' => array('checked' => TRUE),
......@@ -98,79 +73,29 @@ public function buildOptionsForm(&$form, &$form_state) {
);
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, &$form_state, &$options = array()) {
// filter trash out of the options so we don't store giant unnecessary arrays
$options['roles'] = array_filter($options['roles']);
}
public function validateArgument($argument) {
$type = $this->options['type'];
// is_numeric() can return false positives, so we ensure it's an integer.
// However, is_integer() will always fail, since $argument is a string.
if (is_numeric($argument) && $argument == (int)$argument) {
if ($type == 'uid' || $type == 'either') {
if ($argument == \Drupal::currentUser()->id()) {
// If you assign an object to a variable in PHP, the variable
// automatically acts as a reference, not a copy, so we use
// clone to ensure that we don't actually mess with the
// real current user object.
$account = clone \Drupal::currentUser();
}
$condition = 'uid';
}
}
else {
if ($type == 'name' || $type == 'either') {
$name = \Drupal::currentUser()->getUserName() ?: \Drupal::config('user.settings')->get('anonymous');
if ($argument == $name) {
$account = clone \Drupal::currentUser();
}
$condition = 'name';
}
}
// If we don't have a WHERE clause, the argument is invalid.
if (empty($condition)) {
return FALSE;
}
if (!isset($account)) {
$uid = $this->database->select('users', 'u')
->fields('u', array('uid'))
->condition($condition, $argument)
->execute()
->fetchField();
if ($uid === FALSE) {
// User not found.
return FALSE;
}
}
$account = user_load($uid);
/**
* {@inheritdoc}
*/
protected function validateEntity(EntityInterface $entity) {
/** @var \Drupal\user\UserInterface $entity */
$role_check_success = TRUE;
// See if we're filtering users based on roles.
if (!empty($this->options['restrict_roles']) && !empty($this->options['roles'])) {
$roles = $this->options['roles'];
if (!(bool) array_intersect($account->getRoles(), $roles)) {
return FALSE;
if (!(bool) array_intersect($entity->getRoles(), $roles)) {
$role_check_success = FALSE;
}
}
$this->argument->argument = $account->id();
$this->argument->validated_title = check_plain(user_format_name($account));
return TRUE;
}
public function processSummaryArguments(&$args) {
// If the validation says the input is an username, we should reverse the
// argument so it works for example for generation summary urls.
$uids_arg_keys = array_flip($args);
if ($this->options['type'] == 'name') {
$users = user_load_multiple($args);
foreach ($users as $uid => $account) {
$args[$uids_arg_keys[$uid]] = $account->label();
}
}
return $role_check_success && parent::validateEntity($entity);
}
}
<?php
/**
* @file
* Contains \Drupal\user\Plugin\views\argument_validator\UserName.
*/
namespace Drupal\user\Plugin\views\argument_validator;
/**
* Validates whether a user name is valid.
*
* @ViewsArgumentValidator(
* id = "user_name",
* title = @Translation("User name"),
* entity_type = "user"
* )
*/
class UserName extends User {
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, &$form_state) {
parent::buildOptionsForm($form, $form_state);
$entity_type = $this->entityManager->getDefinition('user');
$form['multiple']['#options'] = array(
0 => $this->t('Single name', array('%type' => $entity_type->getLabel())),
1 => $this->t('One or more names separated by , or +', array('%type' => $entity_type->getLabel())),
);
}
/**
* {@inheritdoc}
*/
public function validateArgument($argument) {
if ($this->multipleCapable && $this->options['multiple']) {
// At this point only interested in individual IDs no matter what type,
// just splitting by the allowed delimiters.
$names = array_filter(preg_split('/[,+ ]/', $argument));
}
elseif ($argument) {
$names = array($argument);
}
// No specified argument should be invalid.
else {
return FALSE;
}
$accounts = $this->userStorage->loadByProperties(array('name' => $names));
// If there are no accounts, return FALSE now. As we will not enter the
// loop below otherwise.
if (empty($accounts)) {
return FALSE;
}
// Validate each account. If any fails break out and return false.
foreach ($accounts as $account) {
if (!in_array($account->getUserName(), $names) || !$this->validateEntity($account)) {
return FALSE;
}
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function processSummaryArguments(&$args) {
// If the validation says the input is an username, we should reverse the
// argument so it works for example for generation summary urls.
$uids_arg_keys = array_flip($args);
foreach ($this->userStorage->loadMultiple($args) as $uid => $account) {
$args[$uids_arg_keys[$uid]] = $account->label();
}
}
}
......@@ -19,12 +19,12 @@ class ArgumentValidateTest extends UserTestBase {
*
* @var array
*/
public static $testViews = array('test_view_argument_validate_user');
public static $testViews = array('test_view_argument_validate_user', 'test_view_argument_validate_username');
public static function getInfo() {
return array(
'name' => 'User: Argument validator',
'description' => 'Tests user argument validator.',
'name' => 'User: Argument validators',
'description' => 'Tests user argument validators for ID and name.',
'group' => 'Views module integration',
);
}
......@@ -35,63 +35,36 @@ protected function setUp() {
$this->account = $this->drupalCreateUser();
}
/**
* Tests the User (ID) argument validator.
*/
function testArgumentValidateUserUid() {
$account = $this->account;
// test 'uid' case
$view = $this->view_argument_validate_user('uid');
$view = Views::getView('test_view_argument_validate_user');
$this->executeView($view);
$this->assertTrue($view->argument['null']->validateArgument($account->id()));
// Reset safed argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a string variable since type is 'uid'
$this->assertFalse($view->argument['null']->validateArgument($account->getUsername()));
// Reset safed argument validation.
// Reset argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid numeric, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument(32));
}
function testArgumentValidateUserName() {
/**
* Tests the UserName argument validator.
*/
public function testArgumentValidateUserName() {
$account = $this->account;
// test 'name' case
$view = $this->view_argument_validate_user('name');
$this->assertTrue($view->argument['null']->validateArgument($account->getUsername()));
// Reset safed argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a uid variable since type is 'name'
$this->assertFalse($view->argument['null']->validateArgument($account->id()));
// Reset safed argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid string, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument($this->randomName()));
}
function testArgumentValidateUserEither() {
$account = $this->account;
// test 'either' case
$view = $this->view_argument_validate_user('either');
$view = Views::getView('test_view_argument_validate_username');
$this->executeView($view);
$this->assertTrue($view->argument['null']->validateArgument($account->getUsername()));
// Reset safed argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a uid variable since type is 'name'
$this->assertTrue($view->argument['null']->validateArgument($account->id()));
// Reset safed argument validation.
// Reset argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid string, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument($this->randomName()));
// Reset safed argument validation.
$view->argument['null']->argument_validated = NULL;
// Fail for a valid uid, but for a user that doesn't exist
$this->assertFalse($view->argument['null']->validateArgument(32));
}
function view_argument_validate_user($argtype) {
$view = Views::getView('test_view_argument_validate_user');
$view->setDisplay();
$view->displayHandlers->get('default')->options['arguments']['null']['validate_options']['type'] = $argtype;
$view->preExecute();
$view->initHandlers();
return $view;
}
}
......@@ -16,7 +16,7 @@ display:
style_plugin: default_summary
table: views
validate:
type: user
type: entity:user
plugin_id: 'null'
provider: views
cache:
......
base_table: node
core: '8'
description: ''
status: '1'
display:
default:
display_options:
access:
type: none
arguments:
'null':
default_argument_type: fixed
field: 'null'
id: 'null'
must_not_be: '0'
style_plugin: default_summary
table: views
validate:
type: user_name
plugin_id: 'null'
provider: views
cache:
type: none
exposed_form:
type: basic
pager:
type: full
style:
type: default
row:
type: fields
display_plugin: default
display_title: Master
id: default
position: 0
label: ''
id: test_view_argument_validate_username
tag: ''
......@@ -380,3 +380,12 @@ function user_views_data_alter(&$data) {
),
);
}
/**
* Implements hook_views_plugins_argument_validator_alter().
*/
function user_views_plugins_argument_validator_alter(array &$plugins) {
$plugins['entity:user']['title'] = t('User ID');