Commit 3e2af237 authored by alexpott's avatar alexpott

Issue #2434697 by amateescu, pcambra: Remove UserAutocompleteController

parent 1abfba43
......@@ -136,7 +136,8 @@ public static function validateEntityAutocomplete(array &$element, FormStateInte
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
$autocreate = (bool) $element['#autocreate'];
foreach (Tags::explode($element['#value']) as $input) {
$input_values = $element['#tags'] ? Tags::explode($element['#value']) : array($element['#value']);
foreach ($input_values as $input) {
$match = static::extractEntityIdFromAutocompleteInput($input);
if ($match === NULL) {
// Try to get a match from the input string when the user didn't use
......
......@@ -149,9 +149,15 @@ public function form(array $form, FormStateInterface $form_state) {
'#size' => 30,
);
if ($is_admin) {
$form['author']['name']['#type'] = 'entity_autocomplete';
$form['author']['name']['#target_type'] = 'user';
$form['author']['name']['#selection_settings'] = ['include_anonymous' => FALSE];
$form['author']['name']['#process_default_value'] = FALSE;
// The user name is validated and processed in static::buildEntity() and
// static::validate().
$form['author']['name']['#element_validate'] = array();
$form['author']['name']['#title'] = $this->t('Authored by');
$form['author']['name']['#description'] = $this->t('Leave blank for %anonymous.', array('%anonymous' => $config->get('anonymous')));
$form['author']['name']['#autocomplete_route_name'] = 'user.autocomplete';
}
elseif ($this->currentUser->isAuthenticated()) {
$form['author']['name']['#type'] = 'item';
......
......@@ -390,19 +390,21 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
}
// Default to the anonymous user.
$name = '';
$uid = 0;
if ($new_translation) {
$name = \Drupal::currentUser()->getUsername();
$uid = \Drupal::currentUser()->getAccount()->id();
}
elseif (($account = $metadata->getAuthor()) && $account->id()) {
$name = $account->getUsername();
$uid = $account->id();
}
$form['content_translation']['name'] = array(
'#type' => 'textfield',
$form['content_translation']['uid'] = array(
'#type' => 'entity_autocomplete',
'#title' => t('Authored by'),
'#target_type' => 'user',
'#default_value' => User::load($uid),
// Validation is done by static::entityFormValidate().
'#validate_reference' => FALSE,
'#maxlength' => 60,
'#autocomplete_route_name' => 'user.autocomplete',
'#default_value' => $name,
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => \Drupal::config('user.settings')->get('anonymous'))),
);
......@@ -530,12 +532,8 @@ public function entityFormEntityBuild($entity_type, EntityInterface $entity, arr
$form_langcode = $form_object->getFormLangcode($form_state);
$values = &$form_state->getValue('content_translation', array());
if ($values['name'] == \Drupal::config('user.settings')->get('anonymous')) {
$values['name'] = '';
}
$metadata = $this->manager->getTranslationMetadata($entity);
$metadata->setAuthor(!empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account : User::load(0));
$metadata->setAuthor(!empty($values['uid']) ? User::load($values['uid']) : User::load(0));
$metadata->setPublished(!empty($values['status']));
$metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME);
$metadata->setChangedTime(REQUEST_TIME);
......@@ -560,8 +558,8 @@ function entityFormValidate($form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('content_translation')) {
$translation = $form_state->getValue('content_translation');
// Validate the "authored by" field.
if (!empty($translation['name']) && !($account = user_load_by_name($translation['name']))) {
$form_state->setErrorByName('content_translation][name', t('The translation authoring username %name does not exist.', array('%name' => $translation['name'])));
if (!empty($translation['uid']) && !($account = User::load($translation['uid']))) {
$form_state->setErrorByName('content_translation][uid', t('The translation authoring username %name does not exist.', array('%name' => $account->getUsername())));
}
// Validate the "authored on" field.
if (!empty($translation['created']) && strtotime($translation['created']) === FALSE) {
......
......@@ -219,7 +219,7 @@ protected function doTestAuthoringInfo() {
'created' => REQUEST_TIME - mt_rand(0, 1000),
);
$edit = array(
'content_translation[name]' => $user->getUsername(),
'content_translation[uid]' => $user->getUsername(),
'content_translation[created]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
);
$url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode)));
......@@ -237,7 +237,7 @@ protected function doTestAuthoringInfo() {
$langcode = 'en';
$edit = array(
// User names have by default length 8.
'content_translation[name]' => $this->randomMachineName(12),
'content_translation[uid]' => $this->randomMachineName(12),
'content_translation[created]' => '19/11/1978',
);
$this->drupalPostForm($entity->urlInfo('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode));
......
......@@ -36,3 +36,6 @@ entity_reference.default.handler_settings:
auto_create:
type: boolean
label: 'Create referenced entities if they don''t already exist'
include_anonymous:
type: boolean
label: 'Include the anonymous user in the matched entities.'
......@@ -12,6 +12,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -63,8 +64,8 @@ public static function create(ContainerInterface $container, array $configuratio
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->uid = $this->configuration['owner_uid'];
$entity->save();
/** @var \Drupal\node\NodeInterface $entity */
$entity->setOwnerId($this->configuration['owner_uid'])->save();
}
/**
......@@ -82,32 +83,33 @@ public function defaultConfiguration() {
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$description = t('The username of the user to which you would like to assign ownership.');
$count = $this->connection->query("SELECT COUNT(*) FROM {users}")->fetchField();
$owner_name = '';
if (is_numeric($this->configuration['owner_uid'])) {
$owner_name = $this->connection->query("SELECT name FROM {users_field_data} WHERE uid = :uid AND default_langcode = 1", array(':uid' => $this->configuration['owner_uid']))->fetchField();
}
// Use dropdown for fewer than 200 users; textbox for more than that.
if (intval($count) < 200) {
$options = array();
$result = $this->connection->query("SELECT uid, name FROM {users_field_data} WHERE uid > 0 AND default_langcode = 1 ORDER BY name");
foreach ($result as $data) {
$options[$data->name] = $data->name;
$options[$data->uid] = $data->name;
}
$form['owner_name'] = array(
$form['owner_uid'] = array(
'#type' => 'select',
'#title' => t('Username'),
'#default_value' => $owner_name,
'#default_value' => $this->configuration['owner_uid'],
'#options' => $options,
'#description' => $description,
);
}
else {
$form['owner_name'] = array(
'#type' => 'textfield',
$form['owner_uid'] = array(
'#type' => 'entity_autocomplete',
'#title' => t('Username'),
'#default_value' => $owner_name,
'#autocomplete_route_name' => 'user.autocomplete',
'#target_type' => 'user',
'#selection_setttings' => array(
'include_anonymous' => FALSE,
),
'#default_value' => User::load($this->configuration['owner_uid']),
// Validation is done in static::validateConfigurationForm().
'#validate_reference' => FALSE,
'#size' => '6',
'#maxlength' => '60',
'#description' => $description,
......@@ -120,9 +122,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$exists = (bool) $this->connection->queryRange('SELECT 1 FROM {users_field_data} WHERE name = :name AND default_langcode = 1', 0, 1, array(':name' => $form_state->getValue('owner_name')))->fetchField();
$exists = (bool) $this->connection->queryRange('SELECT 1 FROM {users_field_data} WHERE uid = :uid AND default_langcode = 1', 0, 1, array(':name' => $form_state->getValue('owner_uid')))->fetchField();
if (!$exists) {
$form_state->setErrorByName('owner_name', t('Enter a valid username.'));
$form_state->setErrorByName('owner_uid', t('Enter a valid username.'));
}
}
......@@ -130,7 +132,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['owner_uid'] = $this->connection->query('SELECT uid from {users_field_data} WHERE name = :name AND default_langcode = 1', array(':name' => $form_state->getValue('owner_name')))->fetchField();
$this->configuration['owner_uid'] = $form_state->getValue('owner_uid');
}
/**
......@@ -139,7 +141,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\node\NodeInterface $object */
$result = $object->access('update', $account, TRUE)
->andIf($object->uid->access('edit', $account, TRUE));
->andIf($object->getOwner()->access('edit', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
......
......@@ -282,7 +282,7 @@ public function testInvalidEntityAutocompleteElement() {
]);
$form_builder->submitForm($this, $form_state);
// The input is complete (i.e. contains an entity ID at the ent), no errors
// The input is complete (i.e. contains an entity ID at the end), no errors
// are triggered.
$this->assertEqual(count($form_state->getErrors()), 0);
}
......
......@@ -203,6 +203,7 @@ public function testUserHandler() {
'handler' => 'default',
'handler_settings' => array(
'target_bundles' => array(),
'include_anonymous' => TRUE,
),
);
......@@ -320,6 +321,19 @@ public function testUserHandler() {
),
);
$this->assertReferenceable($selection_options, $referenceable_tests, 'User handler (admin)');
// Test the 'include_anonymous' option.
$selection_options['handler_settings']['include_anonymous'] = FALSE;
$referenceable_tests = array(
array(
'arguments' => array(
array('Anonymous', 'CONTAINS'),
array('anonymous', 'CONTAINS'),
),
'result' => array(),
),
);
$this->assertReferenceable($selection_options, $referenceable_tests, 'User handler (does not include anonymous)');
}
/**
......
......@@ -71,12 +71,11 @@ public function onRequest(GetResponseEvent $event) {
*/
public function onView(GetResponseEvent $event) {
$current_route = $this->currentRouteMatch->getRouteName();
$user_autcomplete_route = array(
'user.autocomplete',
'user.autocomplete_anonymous',
$entity_autcomplete_route = array(
'system.entity_autocomplete',
);
if (in_array($current_route, $user_autcomplete_route)) {
if (in_array($current_route, $entity_autcomplete_route)) {
if ($this->container->initialized('theme.registry')) {
throw new \Exception('registry initialized');
}
......
<?php
/**
* @file
* Contains \Drupal\user\Controller\UserAutocompleteController.
*/
namespace Drupal\user\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\user\UserAutocomplete;
/**
* Controller routines for user routes.
*/
class UserAutocompleteController implements ContainerInjectionInterface {
/**
* The user autocomplete helper class to find matching user names.
*
* @var \Drupal\user\UserAutocomplete
*/
protected $userAutocomplete;
/**
* Constructs an UserAutocompleteController object.
*
* @param \Drupal\user\UserAutocomplete $user_autocomplete
* The user autocomplete helper class to find matching user names.
*/
public function __construct(UserAutocomplete $user_autocomplete) {
$this->userAutocomplete = $user_autocomplete;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.autocomplete')
);
}
/**
* Returns response for the user autocompletion.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object containing the search string.
* @param bool $include_anonymous
* (optional) TRUE if the name used to indicate anonymous users (e.g.
* "Anonymous") should be autocompleted. Defaults to FALSE.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing the autocomplete suggestions for existing users.
*
* @see \Drupal\user\UserAutocomplete::getMatches()
*/
public function autocompleteUser(Request $request, $include_anonymous = FALSE) {
$matches = $this->userAutocomplete->getMatches($request->query->get('q'), $include_anonymous);
return new JsonResponse($matches);
}
/**
* Returns response for the user autocompletion with the anonymous user.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object containing the search string.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing the autocomplete suggestions for existing users.
*
* @see \Drupal\user\UserAutocomplete::autocompleteUser()
*/
public function autocompleteUserAnonymous(Request $request) {
return $this->autocompleteUser($request, TRUE);
}
}
......@@ -86,6 +86,13 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
'filter' => array(
'type' => '_none',
),
'include_anonymous' => TRUE,
);
$form['include_anonymous'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Include the anonymous user.'),
'#default_value' => $selection_handler_settings['include_anonymous'],
);
// Add user specific filter options.
......@@ -156,6 +163,12 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) {
// Bail out early if we do not need to match the Anonymous user.
$handler_settings = $this->configuration['handler_settings'];
if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) {
return;
}
if ($this->currentUser->hasPermission('administer users')) {
// In addition, if the user is administrator, we need to make sure to
// match the anonymous user, that doesn't actually have a name in the
......
......@@ -8,7 +8,9 @@
namespace Drupal\user\Plugin\views\filter;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\Element\EntityAutocomplete;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\User;
use Drupal\views\Plugin\views\filter\InOperator;
/**
......@@ -23,27 +25,16 @@ class Name extends InOperator {
protected $alwaysMultiple = TRUE;
protected function valueForm(&$form, FormStateInterface $form_state) {
$values = array();
if ($this->value) {
$result = entity_load_multiple_by_properties('user', array('uid' => $this->value));
foreach ($result as $account) {
if ($account->id()) {
$values[] = $account->getUsername();
}
else {
$values[] = 'Anonymous'; // Intentionally NOT translated.
}
}
}
sort($values);
$default_value = implode(', ', $values);
$users = $this->value ? User::loadMultiple($this->value) : array();
$default_value = EntityAutocomplete::getEntityLabels($users);
$form['value'] = array(
'#type' => 'textfield',
'#type' => 'entity_autocomplete',
'#title' => $this->t('Usernames'),
'#description' => $this->t('Enter a comma separated list of user names.'),
'#target_type' => 'user',
'#tags' => TRUE,
'#default_value' => $default_value,
'#autocomplete_route_name' => 'user.autocomplete_anonymous',
'#process_default_value' => FALSE,
);
$user_input = $form_state->getUserInput();
......@@ -54,10 +45,14 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
}
protected function valueValidate($form, FormStateInterface $form_state) {
$values = Tags::explode($form_state->getValue(array('options', 'value')));
if ($uids = $this->validate_user_strings($form['value'], $form_state, $values)) {
$form_state->setValue(array('options', 'value'), $uids);
$uids = [];
if ($values = $form_state->getValue(array('options', 'value'))) {
foreach ($values as $value) {
$uids[] = $value['target_id'];
}
sort($uids);
}
$form_state->setValue(array('options', 'value'), $uids);
}
public function acceptExposedInput($input) {
......@@ -90,13 +85,12 @@ public function validateExposed(&$form, FormStateInterface $form_state) {
$input = $this->options['group_info']['group_items'][$input]['value'];
}
$values = Tags::explode($input);
if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) {
$uids = $this->validate_user_strings($form[$identifier], $form_state, $values);
}
else {
$uids = FALSE;
$uids = [];
$values = $form_state->getValue($identifier);
if ($values && (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All')))) {
foreach ($values as $value) {
$uids[] = $value['target_id'];
}
}
if ($uids) {
......@@ -104,43 +98,6 @@ public function validateExposed(&$form, FormStateInterface $form_state) {
}
}
/**
* Validate the user string. Since this can come from either the form
* or the exposed filter, this is abstracted out a bit so it can
* handle the multiple input sources.
*/
function validate_user_strings(&$form, FormStateInterface $form_state, $values) {
$uids = array();
$placeholders = array();
$args = array();
foreach ($values as $value) {
if (strtolower($value) == 'anonymous') {
$uids[] = 0;
}
else {
$missing[strtolower($value)] = TRUE;
$args[] = $value;
$placeholders[] = "'%s'";
}
}
if (!$args) {
return $uids;
}
$result = entity_load_multiple_by_properties('user', array('name' => $args));
foreach ($result as $account) {
unset($missing[strtolower($account->getUsername())]);
$uids[] = $account->id();
}
if ($missing) {
$form_state->setError($form, $this->formatPlural(count($missing), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', array_keys($missing)))));
}
return $uids;
}
protected function valueSubmit($form, FormStateInterface $form_state) {
// prevent array filter from removing our anonymous user.
}
......
<?php
/**
* @file
* Definition of Drupal\user\Tests\UserAutocompleteTest.
*/
namespace Drupal\user\Tests;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Unicode;
use Drupal\simpletest\WebTestBase;
/**
* Tests user autocompletion functionality.
*
* @group user
*/
class UserAutocompleteTest extends WebTestBase {
/**
* A regular user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $unprivilegedUser;
/**
* A user with permission to access user profiles.
*
* @var \Drupal\user\UserInterface
*/
protected $privilegedUser;
protected function setUp() {
parent::setUp();
// Set up two users with different permissions to test access.
$this->unprivilegedUser = $this->drupalCreateUser();
$this->privilegedUser = $this->drupalCreateUser(array('access user profiles'));
}
/**
* Tests access to user autocompletion and verify the correct results.
*/
function testUserAutocomplete() {
// Check access from unprivileged user, should be denied.
$this->drupalLogin($this->unprivilegedUser);
$username = $this->unprivilegedUser->getUsername();
$this->drupalGet('user/autocomplete', array('query' => array('q' => $username[0])));
$this->assertResponse(403, 'Autocompletion access denied to user without permission.');
// Check access from privileged user.
$this->drupalLogout();
$this->drupalLogin($this->privilegedUser);
$this->drupalGet('user/autocomplete', array('query' => array('q' => $username[0])));
$this->assertResponse(200, 'Autocompletion access allowed.');
// Using first letter of the user's name, make sure the user's full name is
// in the results.
$this->assertRaw($this->unprivilegedUser->getUsername(), 'User name found in autocompletion results.');
$anonymous_name = $this->randomString() . '<script>alert();</script>';
$this->config('user.settings')->set('anonymous', $anonymous_name)->save();
// Test that anonymous username is in the result when requested and escaped
// with \Drupal\Component\Utility\String::checkPlain().
$users = $this->drupalGetJSON('user/autocomplete/anonymous', array('query' => array('q' => Unicode::substr($anonymous_name, 0, 4))));
$this->assertEqual(String::checkPlain($anonymous_name), $users[0]['label'], 'The anonymous name found in autocompletion results.');
$users = $this->drupalGetJSON('user/autocomplete', array('query' => array('q' => Unicode::substr($anonymous_name, 0, 4))));
$this->assertTrue(empty($users), 'The anonymous name not found in autocompletion results without enabling anonymous username.');
}
}
......@@ -102,8 +102,7 @@ public function testAdminUserInterface() {
'options[value]' => implode(', ', $users)
);
$this->drupalPostForm($path, $edit, t('Apply'));
$message = \Drupal::translation()->formatPlural(count($users), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $users)));
$this->assertText($message);
$this->assertRaw(t('There are no entities matching "%value".', array('%value' => implode(', ', $users))));
// Pass in an invalid username and a valid username.
$random_name = $this->randomMachineName();
......@@ -114,8 +113,7 @@ public function testAdminUserInterface() {
);
$users = array($users[0]);
$this->drupalPostForm($path, $edit, t('Apply'));
$message = \Drupal::translation()->formatPlural(count($users), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $users)));
$this->assertRaw($message);
$this->assertRaw(t('There are no entities matching "%value".', array('%value' => implode(', ', $users))));
// Pass in just valid usernames.
$users = $this->names;
......@@ -124,8 +122,7 @@ public function testAdminUserInterface() {
'options[value]' => implode(', ', $users)
);
$this->drupalPostForm($path, $edit, t('Apply'));
$message = \Drupal::translation()->formatPlural(count($users), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $users)));
$this->assertNoRaw($message);
$this->assertNoRaw(t('There are no entities matching "%value".', array('%value' => implode(', ', $users))));
}
/**
......@@ -141,18 +138,16 @@ public function testExposedFilter() {
$users = array_map('strtolower', $users);
$options['query']['uid'] = implode(', ', $users);
$this->drupalGet($path, $options);
$message = \Drupal::translation()->formatPlural(count($users), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $users)));
$this->assertRaw($message);
$this->assertRaw(t('There are no entities matching "%value".', array('%value' => implode(', ', $users))));
// Pass in an invalid username and a valid username.
$users = array($this->randomMachineName(), $this->names[0]);
$options['query']['uid'] = implode(', ', $users);