upgrades core to 8.4.2
This commit is contained in:
@@ -166,8 +166,10 @@ condition.plugin.user_role:
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
# Schema for the entity reference 'default:user' selection handler settings.
|
||||
entity_reference_selection.default:user:
|
||||
type: entity_reference_selection
|
||||
type: entity_reference_selection.default
|
||||
label: 'User selection handler settings'
|
||||
mapping:
|
||||
filter:
|
||||
type: mapping
|
||||
|
||||
@@ -19,6 +19,7 @@ source:
|
||||
- user_mail_register_pending_approval_body
|
||||
- user_mail_status_blocked_subject
|
||||
- user_mail_status_blocked_body
|
||||
source_module: user
|
||||
process:
|
||||
'status_activated/subject':
|
||||
plugin: convert_tokens
|
||||
|
||||
@@ -10,6 +10,7 @@ source:
|
||||
- user_email_verification
|
||||
- user_register
|
||||
- anonymous
|
||||
source_module: user
|
||||
process:
|
||||
'notify/status_blocked': user_mail_status_blocked_notify
|
||||
'notify/status_activated': user_mail_status_activated_notify
|
||||
|
||||
@@ -55,4 +55,3 @@ migration_dependencies:
|
||||
- user_picture_field_instance
|
||||
- user_picture_entity_display
|
||||
- user_picture_entity_form_display
|
||||
- d7_field_instance
|
||||
|
||||
@@ -10,6 +10,7 @@ source:
|
||||
- user_failed_login_ip_window
|
||||
- user_failed_login_user_window
|
||||
- user_failed_login_user_limit
|
||||
source_module: user
|
||||
process:
|
||||
uid_only: user_failed_login_identifier_uid_only
|
||||
ip_limit: user_failed_login_ip_limit
|
||||
|
||||
@@ -19,6 +19,7 @@ source:
|
||||
- user_mail_register_pending_approval_body
|
||||
- user_mail_status_blocked_subject
|
||||
- user_mail_status_blocked_body
|
||||
source_module: user
|
||||
process:
|
||||
'status_activated/subject': user_mail_status_activated_subject
|
||||
'status_activated/body': user_mail_status_activated_body
|
||||
|
||||
@@ -12,6 +12,7 @@ source:
|
||||
type: image
|
||||
name: user_picture
|
||||
cardinality: 1
|
||||
source_module: user
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
field_name: 'constants/name'
|
||||
|
||||
@@ -26,7 +26,11 @@ class LoginStatusCheck implements AccessInterface {
|
||||
public function access(AccountInterface $account, Route $route) {
|
||||
$required_status = filter_var($route->getRequirement('_user_is_logged_in'), FILTER_VALIDATE_BOOLEAN);
|
||||
$actual_status = $account->isAuthenticated();
|
||||
return AccessResult::allowedIf($required_status === $actual_status)->addCacheContexts(['user.roles:authenticated']);
|
||||
$access_result = AccessResult::allowedIf($required_status === $actual_status)->addCacheContexts(['user.roles:authenticated']);
|
||||
if (!$access_result->isAllowed()) {
|
||||
$access_result->setReason($required_status === TRUE ? 'This route can only be accessed by authenticated users.' : 'This route can only be accessed by anonymous users.');
|
||||
}
|
||||
return $access_result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ abstract class AccountForm extends ContentEntityForm {
|
||||
'#type' => 'email',
|
||||
'#title' => $this->t('Email address'),
|
||||
'#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by email.'),
|
||||
'#required' => !(!$account->getEmail() && $user->hasPermission('administer users')),
|
||||
'#required' => !(!$account->getEmail() && $admin),
|
||||
'#default_value' => (!$register ? $account->getEmail() : ''),
|
||||
];
|
||||
|
||||
@@ -222,7 +222,7 @@ abstract class AccountForm extends ContentEntityForm {
|
||||
'#open' => TRUE,
|
||||
// Display language selector when either creating a user on the admin
|
||||
// interface or editing a user account.
|
||||
'#access' => !$register || $user->hasPermission('administer users'),
|
||||
'#access' => !$register || $admin,
|
||||
];
|
||||
|
||||
$form['language']['preferred_langcode'] = [
|
||||
|
||||
@@ -10,6 +10,7 @@ use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\user\UserAuthInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -86,6 +87,13 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
|
||||
*/
|
||||
protected $serializerFormats = [];
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a new UserAuthenticationController object.
|
||||
*
|
||||
@@ -103,8 +111,10 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
|
||||
* The serializer.
|
||||
* @param array $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats) {
|
||||
public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
|
||||
$this->flood = $flood;
|
||||
$this->userStorage = $user_storage;
|
||||
$this->csrfToken = $csrf_token;
|
||||
@@ -112,6 +122,7 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
|
||||
$this->serializer = $serializer;
|
||||
$this->serializerFormats = $serializer_formats;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +146,8 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
|
||||
$container->get('user.auth'),
|
||||
$container->get('router.route_provider'),
|
||||
$serializer,
|
||||
$formats
|
||||
$formats,
|
||||
$container->get('logger.factory')->get('user')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,6 +219,56 @@ class UserAuthenticationController extends ControllerBase implements ContainerIn
|
||||
throw new BadRequestHttpException('Sorry, unrecognized username or password.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a user password.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response object.
|
||||
*/
|
||||
public function resetPassword(Request $request) {
|
||||
$format = $this->getRequestFormat($request);
|
||||
|
||||
$content = $request->getContent();
|
||||
$credentials = $this->serializer->decode($content, $format);
|
||||
|
||||
// Check if a name or mail is provided.
|
||||
if (!isset($credentials['name']) && !isset($credentials['mail'])) {
|
||||
throw new BadRequestHttpException('Missing credentials.name or credentials.mail');
|
||||
}
|
||||
|
||||
// Load by name if provided.
|
||||
if (isset($credentials['name'])) {
|
||||
$users = $this->userStorage->loadByProperties(['name' => trim($credentials['name'])]);
|
||||
}
|
||||
elseif (isset($credentials['mail'])) {
|
||||
$users = $this->userStorage->loadByProperties(['mail' => trim($credentials['mail'])]);
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Session\AccountInterface $account */
|
||||
$account = reset($users);
|
||||
if ($account && $account->id()) {
|
||||
if ($this->userIsBlocked($account->getAccountName())) {
|
||||
throw new BadRequestHttpException('The user has not been activated or is blocked.');
|
||||
}
|
||||
|
||||
// Send the password reset email.
|
||||
$mail = _user_mail_notify('password_reset', $account, $account->getPreferredLangcode());
|
||||
if (empty($mail)) {
|
||||
throw new BadRequestHttpException('Unable to send email. Contact the site administrator if the problem persists.');
|
||||
}
|
||||
else {
|
||||
$this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getAccountName(), '%email' => $account->getEmail()]);
|
||||
return new Response();
|
||||
}
|
||||
}
|
||||
|
||||
// Error if no users found with provided name or mail.
|
||||
throw new BadRequestHttpException('Unrecognized username or email address.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the user is blocked.
|
||||
*
|
||||
|
||||
@@ -173,7 +173,7 @@ class Role extends ConfigEntityBase implements RoleInterface {
|
||||
|
||||
if (!isset($this->weight) && ($roles = $storage->loadMultiple())) {
|
||||
// Set a role weight to make this new role last.
|
||||
$max = array_reduce($roles, function($max, $role) {
|
||||
$max = array_reduce($roles, function ($max, $role) {
|
||||
return $max > $role->weight ? $max : $role->weight;
|
||||
});
|
||||
$this->weight = $max + 1;
|
||||
|
||||
@@ -60,9 +60,6 @@ class UserPasswordForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['name'] = [
|
||||
|
||||
@@ -129,14 +129,16 @@ class UserPermissionsForm extends FormBase {
|
||||
|
||||
foreach ($permissions_by_provider as $provider => $permissions) {
|
||||
// Module name.
|
||||
$form['permissions'][$provider] = [[
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => count($role_names) + 1,
|
||||
'class' => ['module'],
|
||||
'id' => 'module-' . $provider,
|
||||
$form['permissions'][$provider] = [
|
||||
[
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => count($role_names) + 1,
|
||||
'class' => ['module'],
|
||||
'id' => 'module-' . $provider,
|
||||
],
|
||||
'#markup' => $this->moduleHandler->getName($provider),
|
||||
],
|
||||
'#markup' => $this->moduleHandler->getName($provider),
|
||||
]];
|
||||
];
|
||||
foreach ($permissions as $perm => $perm_item) {
|
||||
// Fill in default values for the permission.
|
||||
$perm_item += [
|
||||
|
||||
@@ -25,10 +25,14 @@ class UserPermissionsRoleSpecificForm extends UserPermissionsForm {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Builds the user permissions administration form for a specific role.
|
||||
*
|
||||
* @param string $role_id
|
||||
* The user role ID used for this form.
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param \Drupal\user\RoleInterface|null $user_role
|
||||
* (optional) The user role used for this form. Defaults to NULL.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, RoleInterface $user_role = NULL) {
|
||||
$this->userRole = $user_role;
|
||||
|
||||
@@ -6,7 +6,6 @@ use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationTrait;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
@@ -23,7 +22,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
*/
|
||||
class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
use UrlGeneratorTrait;
|
||||
use RedirectDestinationTrait;
|
||||
|
||||
/**
|
||||
@@ -94,7 +92,24 @@ class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterfac
|
||||
unset($form['pass']['#attributes']['aria-describedby']);
|
||||
$form['name']['#size'] = 15;
|
||||
$form['pass']['#size'] = 15;
|
||||
$form['#action'] = $this->url('<current>', [], ['query' => $this->getDestinationArray(), 'external' => FALSE]);
|
||||
|
||||
// Instead of setting an actual action URL, we set the placeholder, which
|
||||
// will be replaced at the very last moment. This ensures forms with
|
||||
// dynamically generated action URLs don't have poor cacheability.
|
||||
// Use the proper API to generate the placeholder, when we have one. See
|
||||
// https://www.drupal.org/node/2562341. The placholder uses a fixed string
|
||||
// that is
|
||||
// Crypt::hashBase64('\Drupal\user\Plugin\Block\UserLoginBlock::build');
|
||||
// This is based on the implementation in
|
||||
// \Drupal\Core\Form\FormBuilder::prepareForm(), but the user login block
|
||||
// requires different behavior for the destination query argument.
|
||||
$placeholder = 'form_action_p_4r8ITd22yaUvXM6SzwrSe9rnQWe48hz9k1Sxto3pBvE';
|
||||
|
||||
$form['#attached']['placeholders'][$placeholder] = [
|
||||
'#lazy_builder' => ['\Drupal\user\Plugin\Block\UserLoginBlock::renderPlaceholderFormAction', []],
|
||||
];
|
||||
$form['#action'] = $placeholder;
|
||||
|
||||
// Build action links.
|
||||
$items = [];
|
||||
if (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
|
||||
@@ -128,4 +143,20 @@ class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterfac
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* #lazy_builder callback; renders a form action URL including destination.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the form action.
|
||||
*
|
||||
* @see \Drupal\Core\Form\FormBuilder::renderPlaceholderFormAction()
|
||||
*/
|
||||
public static function renderPlaceholderFormAction() {
|
||||
return [
|
||||
'#type' => 'markup',
|
||||
'#markup' => Url::fromRoute('<current>', [], ['query' => \Drupal::destination()->getAsArray(), 'external' => FALSE])->toString(),
|
||||
'#cache' => ['contexts' => ['url.path', 'url.query_args']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\user\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
|
||||
@@ -82,21 +83,26 @@ class UserSelection extends DefaultSelection {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$selection_handler_settings = $this->configuration['handler_settings'];
|
||||
|
||||
// Merge in default values.
|
||||
$selection_handler_settings += [
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'filter' => [
|
||||
'type' => '_none',
|
||||
'role' => NULL,
|
||||
],
|
||||
'include_anonymous' => TRUE,
|
||||
];
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
$form['include_anonymous'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Include the anonymous user.'),
|
||||
'#default_value' => $selection_handler_settings['include_anonymous'],
|
||||
'#default_value' => $configuration['include_anonymous'],
|
||||
];
|
||||
|
||||
// Add user specific filter options.
|
||||
@@ -109,7 +115,7 @@ class UserSelection extends DefaultSelection {
|
||||
],
|
||||
'#ajax' => TRUE,
|
||||
'#limit_validation_errors' => [],
|
||||
'#default_value' => $selection_handler_settings['filter']['type'],
|
||||
'#default_value' => $configuration['filter']['type'],
|
||||
];
|
||||
|
||||
$form['filter']['settings'] = [
|
||||
@@ -118,18 +124,13 @@ class UserSelection extends DefaultSelection {
|
||||
'#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']],
|
||||
];
|
||||
|
||||
if ($selection_handler_settings['filter']['type'] == 'role') {
|
||||
// Merge in default values.
|
||||
$selection_handler_settings['filter'] += [
|
||||
'role' => NULL,
|
||||
];
|
||||
|
||||
if ($configuration['filter']['type'] == 'role') {
|
||||
$form['filter']['settings']['role'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Restrict to the selected roles'),
|
||||
'#required' => TRUE,
|
||||
'#options' => array_diff_key(user_role_names(TRUE), [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID]),
|
||||
'#default_value' => $selection_handler_settings['filter']['role'],
|
||||
'#default_value' => $configuration['filter']['role'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -143,11 +144,12 @@ class UserSelection extends DefaultSelection {
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
$handler_settings = $this->configuration['handler_settings'];
|
||||
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
// Filter out the Anonymous user if the selection handler is configured to
|
||||
// exclude it.
|
||||
if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) {
|
||||
if (!$configuration['include_anonymous']) {
|
||||
$query->condition('uid', 0, '<>');
|
||||
}
|
||||
|
||||
@@ -157,8 +159,8 @@ class UserSelection extends DefaultSelection {
|
||||
}
|
||||
|
||||
// Filter by role.
|
||||
if (!empty($handler_settings['filter']['role'])) {
|
||||
$query->condition('roles', $handler_settings['filter']['role'], 'IN');
|
||||
if (!empty($configuration['filter']['role'])) {
|
||||
$query->condition('roles', $configuration['filter']['role'], 'IN');
|
||||
}
|
||||
|
||||
// Adding the permission check is sadly insufficient for users: core
|
||||
@@ -190,10 +192,10 @@ class UserSelection extends DefaultSelection {
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
if (!empty($this->configuration['handler_settings']['filter']['role'])) {
|
||||
$entities = array_filter($entities, function ($user) {
|
||||
if ($role = $this->getConfiguration()['filter']['role']) {
|
||||
$entities = array_filter($entities, function ($user) use ($role) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
return !empty(array_intersect($user->getRoles(), $this->configuration['handler_settings']['filter']['role']));
|
||||
return !empty(array_intersect($user->getRoles(), $role));
|
||||
});
|
||||
}
|
||||
if (!$this->currentUser->hasPermission('administer users')) {
|
||||
@@ -209,9 +211,10 @@ class UserSelection extends DefaultSelection {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityQueryAlter(SelectInterface $query) {
|
||||
parent::entityQueryAlter($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']) {
|
||||
if (!$this->getConfiguration()['include_anonymous']) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,17 +231,17 @@ class UserSelection extends DefaultSelection {
|
||||
// Re-add the condition and a condition on uid = 0 so that we end up
|
||||
// with a query in the form:
|
||||
// WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
|
||||
$or = db_or();
|
||||
$or = new Condition('OR');
|
||||
$or->condition($condition['field'], $condition['value'], $condition['operator']);
|
||||
// Sadly, the Database layer doesn't allow us to build a condition
|
||||
// in the form ':placeholder = :placeholder2', because the 'field'
|
||||
// part of a condition is always escaped.
|
||||
// As a (cheap) workaround, we separately build a condition with no
|
||||
// field, and concatenate the field and the condition separately.
|
||||
$value_part = db_and();
|
||||
$value_part = new Condition('AND');
|
||||
$value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
|
||||
$value_part->compile($this->connection, $query);
|
||||
$or->condition(db_and()
|
||||
$or->condition((new Condition('AND'))
|
||||
->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
|
||||
->condition('base_table.uid', 0)
|
||||
);
|
||||
|
||||
@@ -52,7 +52,7 @@ class UserSearch extends SearchPluginBase implements AccessibleInterface {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static public function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('database'),
|
||||
$container->get('entity.manager'),
|
||||
@@ -162,13 +162,15 @@ class UserSearch extends SearchPluginBase implements AccessibleInterface {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHelp() {
|
||||
$help = ['list' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => [
|
||||
$this->t('User search looks for user names and partial user names. Example: mar would match usernames mar, delmar, and maryjane.'),
|
||||
$this->t('You can use * as a wildcard within your keyword. Example: m*r would match user names mar, delmar, and elementary.'),
|
||||
$help = [
|
||||
'list' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => [
|
||||
$this->t('User search looks for user names and partial user names. Example: mar would match usernames mar, delmar, and maryjane.'),
|
||||
$this->t('You can use * as a wildcard within your keyword. Example: m*r would match user names mar, delmar, and elementary.'),
|
||||
],
|
||||
],
|
||||
]];
|
||||
];
|
||||
|
||||
return $help;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use Drupal\migrate\Row;
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "profile_field",
|
||||
* source_provider = "profile"
|
||||
* source_module = "profile"
|
||||
* )
|
||||
*/
|
||||
class ProfileField extends DrupalSqlBase {
|
||||
|
||||
@@ -11,7 +11,8 @@ use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
|
||||
* @todo Support default picture?
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "user_picture_instance"
|
||||
* id = "user_picture_instance",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPictureInstance extends DrupalSqlBase {
|
||||
@@ -28,7 +29,8 @@ class UserPictureInstance extends DrupalSqlBase {
|
||||
'file_directory' => $this->variableGet('user_picture_path', 'pictures'),
|
||||
'max_filesize' => $this->variableGet('user_picture_file_size', '30') . 'KB',
|
||||
'max_resolution' => $this->variableGet('user_picture_dimensions', '85x85'),
|
||||
]]);
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_profile_field_values",
|
||||
* source_provider = "profile"
|
||||
* source_module = "profile"
|
||||
* )
|
||||
*/
|
||||
class ProfileFieldValues extends DrupalSqlBase {
|
||||
|
||||
@@ -9,7 +9,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
* Drupal 6 role source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_role"
|
||||
* id = "d6_user_role",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class Role extends DrupalSqlBase {
|
||||
|
||||
@@ -9,7 +9,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
* Drupal 6 user source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user"
|
||||
* id = "d6_user",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class User extends DrupalSqlBase {
|
||||
|
||||
@@ -10,7 +10,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
* @todo Support default picture?
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_picture"
|
||||
* id = "d6_user_picture",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPicture extends DrupalSqlBase {
|
||||
|
||||
@@ -9,7 +9,8 @@ use Drupal\migrate\Row;
|
||||
* Drupal 6 user picture source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_picture_file"
|
||||
* id = "d6_user_picture_file",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPictureFile extends DrupalSqlBase {
|
||||
|
||||
@@ -9,7 +9,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
* Drupal 7 role source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_user_role"
|
||||
* id = "d7_user_role",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class Role extends DrupalSqlBase {
|
||||
|
||||
@@ -9,7 +9,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
* Drupal 7 user source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_user"
|
||||
* id = "d7_user",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class User extends FieldableEntity {
|
||||
|
||||
@@ -51,10 +51,10 @@ class Uid extends NumericArgument {
|
||||
* Override the behavior of title(). Get the name of the user.
|
||||
*
|
||||
* @return array
|
||||
* A list of usernames.
|
||||
* A list of usernames.
|
||||
*/
|
||||
public function titleQuery() {
|
||||
return array_map(function($account) {
|
||||
return array_map(function ($account) {
|
||||
return $account->label();
|
||||
}, $this->storage->loadMultiple($this->value));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\user\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\filter\BooleanOperator;
|
||||
@@ -28,7 +29,7 @@ class Current extends BooleanOperator {
|
||||
$this->ensureMyTable();
|
||||
|
||||
$field = $this->tableAlias . '.' . $this->realField . ' ';
|
||||
$or = db_or();
|
||||
$or = new Condition('OR');
|
||||
|
||||
if (empty($this->value)) {
|
||||
$or->condition($field, '***CURRENT_USER***', '<>');
|
||||
|
||||
@@ -115,7 +115,8 @@ class Name extends InOperator {
|
||||
$this->valueOptions[$account->id()] = $account->label();
|
||||
}
|
||||
else {
|
||||
$this->valueOptions[$account->id()] = 'Anonymous'; // Intentionally NOT translated.
|
||||
// Intentionally NOT translated.
|
||||
$this->valueOptions[$account->id()] = 'Anonymous';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class PrivateTempStoreFactory {
|
||||
/**
|
||||
* The lock object used for this data.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface $lockBackend
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lockBackend;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class SharedTempStoreFactory {
|
||||
/**
|
||||
* The lock object used for this data.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface $lockBackend
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lockBackend;
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ class RestRegisterUserTest extends RESTTestBase {
|
||||
* @param bool $include_password
|
||||
* Whether to include a password in the user values.
|
||||
*
|
||||
* @return string Serialized user values.
|
||||
* @return string
|
||||
* Serialized user values.
|
||||
*/
|
||||
protected function createSerializedUser($name, $include_password = TRUE) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\user\Tests;
|
||||
|
||||
use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
@@ -77,10 +78,30 @@ class UserBlocksTest extends WebTestBase {
|
||||
|
||||
// Now, log out and repeat with a non-403 page.
|
||||
$this->drupalLogout();
|
||||
$this->drupalPostForm('filter/tips', $edit, t('Log in'));
|
||||
$this->drupalGet('filter/tips');
|
||||
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
|
||||
$this->drupalPostForm(NULL, $edit, t('Log in'));
|
||||
$this->assertNoText(t('User login'), 'Logged in.');
|
||||
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
|
||||
|
||||
// Log out again and repeat with a non-403 page including query arguments.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('filter/tips', ['query' => ['foo' => 'bar']]);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
|
||||
$this->drupalPostForm(NULL, $edit, t('Log in'));
|
||||
$this->assertNoText(t('User login'), 'Logged in.');
|
||||
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
|
||||
$this->assertTrue(strpos($this->getUrl(), '/filter/tips?foo=bar') !== FALSE, 'Correct query arguments are displayed after login');
|
||||
|
||||
// Repeat with different query arguments.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('filter/tips', ['query' => ['foo' => 'baz']]);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER));
|
||||
$this->drupalPostForm(NULL, $edit, t('Log in'));
|
||||
$this->assertNoText(t('User login'), 'Logged in.');
|
||||
$this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');
|
||||
$this->assertTrue(strpos($this->getUrl(), '/filter/tips?foo=baz') !== FALSE, 'Correct query arguments are displayed after login');
|
||||
|
||||
// Check that the user login block is not vulnerable to information
|
||||
// disclosure to third party sites.
|
||||
$this->drupalLogout();
|
||||
|
||||
@@ -130,7 +130,7 @@ class UserPasswordResetTest extends PageCacheTagsTestBase {
|
||||
|
||||
// Verify that the password reset session has been destroyed.
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertText(t('Your current password is missing or incorrect; it\'s required to change the Password.'), 'Password needed to make profile changes.');
|
||||
$this->assertText(t("Your current password is missing or incorrect; it's required to change the Password."), 'Password needed to make profile changes.');
|
||||
|
||||
// Log out, and try to log in again using the same one-time link.
|
||||
$this->drupalLogout();
|
||||
@@ -144,7 +144,7 @@ class UserPasswordResetTest extends PageCacheTagsTestBase {
|
||||
$before = count($this->drupalGetMails(['id' => 'user_password_reset']));
|
||||
$edit = ['name' => $this->account->getEmail()];
|
||||
$this->drupalPostForm(NULL, $edit, t('Submit'));
|
||||
$this->assertTrue( count($this->drupalGetMails(['id' => 'user_password_reset'])) === $before + 1, 'Email sent when requesting password reset using email address.');
|
||||
$this->assertTrue(count($this->drupalGetMails(['id' => 'user_password_reset'])) === $before + 1, 'Email sent when requesting password reset using email address.');
|
||||
|
||||
// Visit the user edit page without pass-reset-token and make sure it does
|
||||
// not cause an error.
|
||||
|
||||
@@ -33,6 +33,12 @@ class UserPictureTest extends WebTestBase {
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// This test expects unused managed files to be marked temporary and then
|
||||
// cleaned up by file_cron().
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'access comments',
|
||||
|
||||
@@ -254,7 +254,7 @@ class UserRegistrationTest extends WebTestBase {
|
||||
$new_user = reset($accounts);
|
||||
$this->assertEqual($new_user->getUsername(), $name, 'Username matches.');
|
||||
$this->assertEqual($new_user->getEmail(), $mail, 'Email address matches.');
|
||||
$this->assertTrue(($new_user->getCreatedTime() > REQUEST_TIME - 20 ), 'Correct creation time.');
|
||||
$this->assertTrue(($new_user->getCreatedTime() > REQUEST_TIME - 20), 'Correct creation time.');
|
||||
$this->assertEqual($new_user->isActive(), $config_user_settings->get('register') == USER_REGISTER_VISITORS ? 1 : 0, 'Correct status field.');
|
||||
$this->assertEqual($new_user->getTimezone(), $config_system_date->get('timezone.default'), 'Correct time zone field.');
|
||||
$this->assertEqual($new_user->langcode->value, \Drupal::languageManager()->getDefaultLanguage()->getId(), 'Correct language field.');
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Tests;
|
||||
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
|
||||
/**
|
||||
* Helper function for logging in from reset password email.
|
||||
*/
|
||||
trait UserResetEmailTestTrait {
|
||||
|
||||
use AssertMailTrait {
|
||||
getMails as drupalGetMails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login from reset password email.
|
||||
*/
|
||||
protected function loginFromResetEmail() {
|
||||
$_emails = $this->drupalGetMails();
|
||||
$email = end($_emails);
|
||||
$urls = [];
|
||||
preg_match('#.+user/reset/.+#', $email['body'], $urls);
|
||||
$resetURL = $urls[0];
|
||||
$this->drupalGet($resetURL);
|
||||
$this->drupalPostForm(NULL, NULL, 'Log in');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -126,10 +126,10 @@ class AccessRoleTest extends AccessTestBase {
|
||||
$account_switcher->switchTo($this->webUser);
|
||||
$result = $renderer->renderPlain($build);
|
||||
// @todo Fix this in https://www.drupal.org/node/2551037,
|
||||
// DisplayPluginBase::applyDisplayCachablityMetadata() is not invoked when
|
||||
// DisplayPluginBase::applyDisplayCacheabilityMetadata() is not invoked when
|
||||
// using buildBasicRenderable() and a Views access plugin returns FALSE.
|
||||
//$this->assertTrue(in_array('user.roles', $build['#cache']['contexts']));
|
||||
//$this->assertEqual([], $build['#cache']['tags']);
|
||||
// $this->assertTrue(in_array('user.roles', $build['#cache']['contexts']));
|
||||
// $this->assertEqual([], $build['#cache']['tags']);
|
||||
$this->assertEqual(Cache::PERMANENT, $build['#cache']['max-age']);
|
||||
$this->assertEqual($result, '');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Tests\Views;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ class HandlerFilterUserNameTest extends ViewTestBase {
|
||||
}
|
||||
|
||||
// Pass in just valid user IDs in the entity_autocomplete target_id format.
|
||||
$options['query']['uid'] = array_map(function($account) {
|
||||
$options['query']['uid'] = array_map(function ($account) {
|
||||
return ['target_id' => $account->id()];
|
||||
}, $this->accounts);
|
||||
|
||||
|
||||
@@ -78,10 +78,9 @@ class UserData implements UserDataInterface {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($module, $uid, $name, $value) {
|
||||
$serialized = 0;
|
||||
if (!is_scalar($value)) {
|
||||
$serialized = (int) !is_scalar($value);
|
||||
if ($serialized) {
|
||||
$value = serialize($value);
|
||||
$serialized = 1;
|
||||
}
|
||||
$this->connection->merge('users_data')
|
||||
->keys([
|
||||
|
||||
@@ -5,8 +5,8 @@ package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@ package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -5,8 +5,8 @@ package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -5,8 +5,8 @@ package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -8,8 +8,8 @@ dependencies:
|
||||
- user
|
||||
- views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests user email token upgrade path.
|
||||
*
|
||||
* @group Update
|
||||
*/
|
||||
class UserUpdateEmailToken extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/drupal-8.user-email-token-2587275.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that email token in status_blocked of user.mail is updated.
|
||||
*/
|
||||
public function testEmailToken() {
|
||||
$mail = \Drupal::config('user.mail')->get('status_blocked');
|
||||
$this->assertTrue(strpos($mail['body'], '[site:account-name]'));
|
||||
$this->runUpdates();
|
||||
$mail = \Drupal::config('user.mail')->get('status_blocked');
|
||||
$this->assertFalse(strpos($mail['body'], '[site:account-name]'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\user\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests user permissions sort upgrade path.
|
||||
*
|
||||
* @group Update
|
||||
*/
|
||||
class UserUpdateOrderPermissionsTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that permissions are ordered by machine name.
|
||||
*/
|
||||
public function testPermissionsOrder() {
|
||||
$authenticated = Role::load('authenticated');
|
||||
$permissions = $authenticated->getPermissions();
|
||||
sort($permissions);
|
||||
$this->assertNotIdentical($permissions, $authenticated->getPermissions());
|
||||
|
||||
$this->runUpdates();
|
||||
$authenticated = Role::load('authenticated');
|
||||
$this->assertIdentical($permissions, $authenticated->getPermissions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -328,7 +328,8 @@ class UserCancelTest extends BrowserTestBase {
|
||||
$revision = $revision_node->getRevisionId();
|
||||
$settings = get_object_vars($revision_node);
|
||||
$settings['revision'] = 1;
|
||||
$settings['uid'] = 1; // Set new/current revision to someone else.
|
||||
// Set new/current revision to someone else.
|
||||
$settings['uid'] = 1;
|
||||
$revision_node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Attempt to cancel account.
|
||||
@@ -454,7 +455,8 @@ class UserCancelTest extends BrowserTestBase {
|
||||
$revision = $revision_node->getRevisionId();
|
||||
$settings = get_object_vars($revision_node);
|
||||
$settings['revision'] = 1;
|
||||
$settings['uid'] = 1; // Set new/current revision to someone else.
|
||||
// Set new/current revision to someone else.
|
||||
$settings['uid'] = 1;
|
||||
$revision_node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Attempt to cancel account.
|
||||
|
||||
@@ -6,6 +6,7 @@ use Drupal\Core\Flood\DatabaseBackend;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Controller\UserAuthenticationController;
|
||||
use Drupal\user\Tests\UserResetEmailTestTrait;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
@@ -14,12 +15,14 @@ use Drupal\hal\Encoder\JsonEncoder as HALJsonEncoder;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* Tests login via direct HTTP.
|
||||
* Tests login and password reset via direct HTTP.
|
||||
*
|
||||
* @group user
|
||||
*/
|
||||
class UserLoginHttpTest extends BrowserTestBase {
|
||||
|
||||
use UserResetEmailTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
@@ -61,7 +64,7 @@ class UserLoginHttpTest extends BrowserTestBase {
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface The HTTP response.
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function loginRequest($name, $pass, $format = 'json') {
|
||||
@@ -178,6 +181,11 @@ class UserLoginHttpTest extends BrowserTestBase {
|
||||
$this->assertEquals($account->getRoles(), $result_data['current_user']['roles']);
|
||||
$logout_token = $result_data['logout_token'];
|
||||
|
||||
// Logging in while already logged in results in a 403 with helpful message.
|
||||
$response = $this->loginRequest($name, $pass, $format);
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
$this->assertSame(['message' => 'This route can only be accessed by anonymous users.'], $this->serializer->decode($response->getBody(), $format));
|
||||
|
||||
$response = $client->get($login_status_url, ['cookies' => $this->cookies]);
|
||||
$this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
|
||||
|
||||
@@ -190,6 +198,52 @@ class UserLoginHttpTest extends BrowserTestBase {
|
||||
$this->resetFlood();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a password HTTP request.
|
||||
*
|
||||
* @param array $request_body
|
||||
* The request body.
|
||||
* @param string $format
|
||||
* The format to use to make the request.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function passwordRequest(array $request_body, $format = 'json') {
|
||||
$password_reset_url = Url::fromRoute('user.pass.http')
|
||||
->setRouteParameter('_format', $format)
|
||||
->setAbsolute();
|
||||
|
||||
$result = \Drupal::httpClient()->post($password_reset_url->toString(), [
|
||||
'body' => $this->serializer->encode($request_body, $format),
|
||||
'headers' => [
|
||||
'Accept' => "application/$format",
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
'cookies' => $this->cookies,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests user password reset.
|
||||
*/
|
||||
public function testPasswordReset() {
|
||||
// Create a user account.
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
// Without the serialization module only JSON is supported.
|
||||
$this->doTestPasswordReset('json', $account);
|
||||
|
||||
// Enable serialization so we have access to additional formats.
|
||||
$this->container->get('module_installer')->install(['serialization']);
|
||||
|
||||
$this->doTestPasswordReset('json', $account);
|
||||
$this->doTestPasswordReset('xml', $account);
|
||||
$this->doTestPasswordReset('hal_json', $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value for a given key from the response.
|
||||
*
|
||||
@@ -346,7 +400,7 @@ class UserLoginHttpTest extends BrowserTestBase {
|
||||
* @param string $logout_token
|
||||
* The csrf token for user logout.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface The HTTP response.
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* The HTTP response.
|
||||
*/
|
||||
protected function logoutRequest($format = 'json', $logout_token = '') {
|
||||
@@ -429,4 +483,47 @@ class UserLoginHttpTest extends BrowserTestBase {
|
||||
return $user_login_status_url->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do password reset testing for given format and account.
|
||||
*
|
||||
* @param string $format
|
||||
* Serialization format.
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* Test account.
|
||||
*/
|
||||
protected function doTestPasswordReset($format, $account) {
|
||||
$response = $this->passwordRequest([], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name or credentials.mail', $format);
|
||||
|
||||
$response = $this->passwordRequest(['name' => 'dramallama'], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
|
||||
|
||||
$response = $this->passwordRequest(['mail' => 'llama@drupal.org'], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
|
||||
|
||||
$account
|
||||
->block()
|
||||
->save();
|
||||
|
||||
$response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
|
||||
|
||||
$response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
|
||||
$this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
|
||||
|
||||
$account
|
||||
->activate()
|
||||
->save();
|
||||
|
||||
$response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->loginFromResetEmail();
|
||||
$this->drupalLogout();
|
||||
|
||||
$response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->loginFromResetEmail();
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace Drupal\Tests\user\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\migrate\Kernel\NodeCommentCombinationTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\RoleInterface;
|
||||
@@ -18,6 +17,8 @@ use Drupal\user\UserInterface;
|
||||
*/
|
||||
class MigrateUserTest extends MigrateDrupal7TestBase {
|
||||
|
||||
use NodeCommentCombinationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -43,12 +44,12 @@ class MigrateUserTest extends MigrateDrupal7TestBase {
|
||||
|
||||
// Prepare to migrate user pictures as well.
|
||||
$this->installEntitySchema('file');
|
||||
$this->createType('page');
|
||||
$this->createType('article');
|
||||
$this->createType('blog');
|
||||
$this->createType('book');
|
||||
$this->createType('forum');
|
||||
$this->createType('test_content_type');
|
||||
$this->createNodeCommentCombination('page');
|
||||
$this->createNodeCommentCombination('article');
|
||||
$this->createNodeCommentCombination('blog');
|
||||
$this->createNodeCommentCombination('book');
|
||||
$this->createNodeCommentCombination('forum', 'comment_forum');
|
||||
$this->createNodeCommentCombination('test_content_type');
|
||||
Vocabulary::create(['vid' => 'test_vocabulary'])->save();
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
@@ -61,25 +62,6 @@ class MigrateUserTest extends MigrateDrupal7TestBase {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node type with a corresponding comment type.
|
||||
*
|
||||
* @param string $id
|
||||
* The node type ID.
|
||||
*/
|
||||
protected function createType($id) {
|
||||
NodeType::create([
|
||||
'type' => $id,
|
||||
'label' => $this->randomString(),
|
||||
])->save();
|
||||
|
||||
CommentType::create([
|
||||
'id' => 'comment_node_' . $id,
|
||||
'label' => $this->randomString(),
|
||||
'target_entity_type_id' => 'node',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a user account.
|
||||
*
|
||||
|
||||
@@ -40,23 +40,29 @@ class UserValidationTest extends KernelTestBase {
|
||||
* Tests user name validation.
|
||||
*/
|
||||
public function testUsernames() {
|
||||
$test_cases = [ // '<username>' => array('<description>', 'assert<testName>'),
|
||||
$test_cases = [
|
||||
// '<username>' => ['<description>', 'assert<testName>'].
|
||||
'foo' => ['Valid username', 'assertNull'],
|
||||
'FOO' => ['Valid username', 'assertNull'],
|
||||
'Foo O\'Bar' => ['Valid username', 'assertNull'],
|
||||
'foo@bar' => ['Valid username', 'assertNull'],
|
||||
'foo@example.com' => ['Valid username', 'assertNull'],
|
||||
'foo@-example.com' => ['Valid username', 'assertNull'], // invalid domains are allowed in usernames
|
||||
// invalid domains are allowed in usernames.
|
||||
'foo@-example.com' => ['Valid username', 'assertNull'],
|
||||
'þòøÇߪř€' => ['Valid username', 'assertNull'],
|
||||
'foo+bar' => ['Valid username', 'assertNull'], // '+' symbol is allowed
|
||||
'ᚠᛇᚻ᛫ᛒᛦᚦ' => ['Valid UTF8 username', 'assertNull'], // runes
|
||||
// '+' symbol is allowed.
|
||||
'foo+bar' => ['Valid username', 'assertNull'],
|
||||
// runes.
|
||||
'ᚠᛇᚻ᛫ᛒᛦᚦ' => ['Valid UTF8 username', 'assertNull'],
|
||||
' foo' => ['Invalid username that starts with a space', 'assertNotNull'],
|
||||
'foo ' => ['Invalid username that ends with a space', 'assertNotNull'],
|
||||
'foo bar' => ['Invalid username that contains 2 spaces \' \'', 'assertNotNull'],
|
||||
'' => ['Invalid empty username', 'assertNotNull'],
|
||||
'foo/' => ['Invalid username containing invalid chars', 'assertNotNull'],
|
||||
'foo' . chr(0) . 'bar' => ['Invalid username containing chr(0)', 'assertNotNull'], // NULL
|
||||
'foo' . chr(13) . 'bar' => ['Invalid username containing chr(13)', 'assertNotNull'], // CR
|
||||
// NULL.
|
||||
'foo' . chr(0) . 'bar' => ['Invalid username containing chr(0)', 'assertNotNull'],
|
||||
// CR.
|
||||
'foo' . chr(13) . 'bar' => ['Invalid username containing chr(13)', 'assertNotNull'],
|
||||
str_repeat('x', USERNAME_MAX_LENGTH + 1) => ['Invalid excessively long username', 'assertNotNull'],
|
||||
];
|
||||
foreach ($test_cases as $name => $test_case) {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\user\Traits;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Provides methods to create additional test users and switch the currently
|
||||
* logged in one.
|
||||
*
|
||||
* This trait is meant to be used only by test classes.
|
||||
*/
|
||||
trait UserCreationTrait {
|
||||
|
||||
/**
|
||||
* Switch the current logged in user.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user account object.
|
||||
*/
|
||||
protected function setCurrentUser(AccountInterface $account) {
|
||||
\Drupal::currentUser()->setAccount($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with a given set of permissions.
|
||||
*
|
||||
* @param array $permissions
|
||||
* Array of permission names to assign to user. Note that the user always
|
||||
* has the default permissions derived from the "authenticated users" role.
|
||||
* @param string $name
|
||||
* The user name.
|
||||
* @param bool $admin
|
||||
* (optional) Whether the user should be an administrator
|
||||
* with all the available permissions.
|
||||
*
|
||||
* @return \Drupal\user\Entity\User|false
|
||||
* A fully loaded user object with pass_raw property, or FALSE if account
|
||||
* creation fails.
|
||||
*/
|
||||
protected function createUser(array $permissions = [], $name = NULL, $admin = FALSE) {
|
||||
// Create a role with the given permission set, if any.
|
||||
$rid = FALSE;
|
||||
if ($permissions) {
|
||||
$rid = $this->createRole($permissions);
|
||||
if (!$rid) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a user assigned to that role.
|
||||
$edit = [];
|
||||
$edit['name'] = !empty($name) ? $name : $this->randomMachineName();
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$edit['pass'] = user_password();
|
||||
$edit['status'] = 1;
|
||||
if ($rid) {
|
||||
$edit['roles'] = [$rid];
|
||||
}
|
||||
|
||||
if ($admin) {
|
||||
$edit['roles'][] = $this->createAdminRole();
|
||||
}
|
||||
|
||||
$account = User::create($edit);
|
||||
$account->save();
|
||||
|
||||
$this->assertTrue($account->id(), SafeMarkup::format('User created with name %name and pass %pass', ['%name' => $edit['name'], '%pass' => $edit['pass']]), 'User login');
|
||||
if (!$account->id()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Add the raw password so that we can log in as this user.
|
||||
$account->pass_raw = $edit['pass'];
|
||||
// Support BrowserTestBase as well.
|
||||
$account->passRaw = $account->pass_raw;
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an administrative role.
|
||||
*
|
||||
* @param string $rid
|
||||
* (optional) The role ID (machine name). Defaults to a random name.
|
||||
* @param string $name
|
||||
* (optional) The label for the role. Defaults to a random string.
|
||||
* @param int $weight
|
||||
* (optional) The weight for the role. Defaults NULL so that entity_create()
|
||||
* sets the weight to maximum + 1.
|
||||
*
|
||||
* @return string
|
||||
* Role ID of newly created role, or FALSE if role creation failed.
|
||||
*/
|
||||
protected function createAdminRole($rid = NULL, $name = NULL, $weight = NULL) {
|
||||
$rid = $this->createRole([], $rid, $name, $weight);
|
||||
if ($rid) {
|
||||
/** @var \Drupal\user\RoleInterface $role */
|
||||
$role = Role::load($rid);
|
||||
$role->setIsAdmin(TRUE);
|
||||
$role->save();
|
||||
}
|
||||
return $rid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a role with specified permissions.
|
||||
*
|
||||
* @param array $permissions
|
||||
* Array of permission names to assign to role.
|
||||
* @param string $rid
|
||||
* (optional) The role ID (machine name). Defaults to a random name.
|
||||
* @param string $name
|
||||
* (optional) The label for the role. Defaults to a random string.
|
||||
* @param int $weight
|
||||
* (optional) The weight for the role. Defaults NULL so that entity_create()
|
||||
* sets the weight to maximum + 1.
|
||||
*
|
||||
* @return string
|
||||
* Role ID of newly created role, or FALSE if role creation failed.
|
||||
*/
|
||||
protected function createRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) {
|
||||
// Generate a random, lowercase machine name if none was passed.
|
||||
if (!isset($rid)) {
|
||||
$rid = strtolower($this->randomMachineName(8));
|
||||
}
|
||||
// Generate a random label.
|
||||
if (!isset($name)) {
|
||||
// In the role UI role names are trimmed and random string can start or
|
||||
// end with a space.
|
||||
$name = trim($this->randomString(8));
|
||||
}
|
||||
|
||||
// Check the all the permissions strings are valid.
|
||||
if (!$this->checkPermissions($permissions)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Create new role.
|
||||
$role = Role::create([
|
||||
'id' => $rid,
|
||||
'label' => $name,
|
||||
]);
|
||||
if (isset($weight)) {
|
||||
$role->set('weight', $weight);
|
||||
}
|
||||
$result = $role->save();
|
||||
|
||||
$this->assertIdentical($result, SAVED_NEW, SafeMarkup::format('Created role ID @rid with name @name.', [
|
||||
'@name' => var_export($role->label(), TRUE),
|
||||
'@rid' => var_export($role->id(), TRUE),
|
||||
]), 'Role');
|
||||
|
||||
if ($result === SAVED_NEW) {
|
||||
// Grant the specified permissions to the role, if any.
|
||||
if (!empty($permissions)) {
|
||||
$this->grantPermissions($role, $permissions);
|
||||
$assigned_permissions = Role::load($role->id())->getPermissions();
|
||||
$missing_permissions = array_diff($permissions, $assigned_permissions);
|
||||
if (!$missing_permissions) {
|
||||
$this->pass(SafeMarkup::format('Created permissions: @perms', ['@perms' => implode(', ', $permissions)]), 'Role');
|
||||
}
|
||||
else {
|
||||
$this->fail(SafeMarkup::format('Failed to create permissions: @perms', ['@perms' => implode(', ', $missing_permissions)]), 'Role');
|
||||
}
|
||||
}
|
||||
return $role->id();
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given list of permission names is valid.
|
||||
*
|
||||
* @param array $permissions
|
||||
* The permission names to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the permissions are valid, FALSE otherwise.
|
||||
*/
|
||||
protected function checkPermissions(array $permissions) {
|
||||
$available = array_keys(\Drupal::service('user.permissions')->getPermissions());
|
||||
$valid = TRUE;
|
||||
foreach ($permissions as $permission) {
|
||||
if (!in_array($permission, $available)) {
|
||||
$this->fail(SafeMarkup::format('Invalid permission %permission.', ['%permission' => $permission]), 'Role');
|
||||
$valid = FALSE;
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant permissions to a user role.
|
||||
*
|
||||
* @param \Drupal\user\RoleInterface $role
|
||||
* The ID of a user role to alter.
|
||||
* @param array $permissions
|
||||
* (optional) A list of permission names to grant.
|
||||
*/
|
||||
protected function grantPermissions(RoleInterface $role, array $permissions) {
|
||||
foreach ($permissions as $permission) {
|
||||
$role->grantPermission($permission);
|
||||
}
|
||||
$role->trustData()->save();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,7 +69,9 @@ class UserLocalTasksTest extends LocalTaskIntegrationTestBase {
|
||||
$tasks = [
|
||||
0 => ['entity.user.canonical', 'entity.user.edit_form'],
|
||||
];
|
||||
if ($subtask) $tasks[] = $subtask;
|
||||
if ($subtask) {
|
||||
$tasks[] = $subtask;
|
||||
}
|
||||
$this->assertLocalTasks($route, $tasks);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ description: 'Theme for testing the available fields in user twig template'
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @file
|
||||
* User behaviors.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, drupalSettings) {
|
||||
/**
|
||||
* Attach handlers to evaluate the strength of any password fields and to
|
||||
* check that its confirmation is correct.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches password strength indicator and other relevant validation to
|
||||
* password fields.
|
||||
*/
|
||||
Drupal.behaviors.password = {
|
||||
attach(context, settings) {
|
||||
const $passwordInput = $(context).find('input.js-password-field').once('password');
|
||||
|
||||
if ($passwordInput.length) {
|
||||
const translate = settings.password;
|
||||
|
||||
const $passwordInputParent = $passwordInput.parent();
|
||||
const $passwordInputParentWrapper = $passwordInputParent.parent();
|
||||
let $passwordSuggestions;
|
||||
|
||||
// Add identifying class to password element parent.
|
||||
$passwordInputParent.addClass('password-parent');
|
||||
|
||||
// Add the password confirmation layer.
|
||||
$passwordInputParentWrapper
|
||||
.find('input.js-password-confirm')
|
||||
.parent()
|
||||
.append(`<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">${translate.confirmTitle} <span></span></div>`)
|
||||
.addClass('confirm-parent');
|
||||
|
||||
const $confirmInput = $passwordInputParentWrapper.find('input.js-password-confirm');
|
||||
const $confirmResult = $passwordInputParentWrapper.find('div.js-password-confirm');
|
||||
const $confirmChild = $confirmResult.find('span');
|
||||
|
||||
// If the password strength indicator is enabled, add its markup.
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
const passwordMeter = `<div class="password-strength"><div class="password-strength__meter"><div class="password-strength__indicator js-password-strength__indicator"></div></div><div aria-live="polite" aria-atomic="true" class="password-strength__title">${translate.strengthTitle} <span class="password-strength__text js-password-strength__text"></span></div></div>`;
|
||||
$confirmInput.parent().after('<div class="password-suggestions description"></div>');
|
||||
$passwordInputParent.append(passwordMeter);
|
||||
$passwordSuggestions = $passwordInputParentWrapper.find('div.password-suggestions').hide();
|
||||
}
|
||||
|
||||
// Check that password and confirmation inputs match.
|
||||
const passwordCheckMatch = function (confirmInputVal) {
|
||||
const success = $passwordInput.val() === confirmInputVal;
|
||||
const confirmClass = success ? 'ok' : 'error';
|
||||
|
||||
// Fill in the success message and set the class accordingly.
|
||||
$confirmChild.html(translate[`confirm${success ? 'Success' : 'Failure'}`])
|
||||
.removeClass('ok error').addClass(confirmClass);
|
||||
};
|
||||
|
||||
// Check the password strength.
|
||||
const passwordCheck = function () {
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
// Evaluate the password strength.
|
||||
const result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
|
||||
|
||||
// Update the suggestions for how to improve the password.
|
||||
if ($passwordSuggestions.html() !== result.message) {
|
||||
$passwordSuggestions.html(result.message);
|
||||
}
|
||||
|
||||
// Only show the description box if a weakness exists in the
|
||||
// password.
|
||||
$passwordSuggestions.toggle(result.strength !== 100);
|
||||
|
||||
// Adjust the length of the strength indicator.
|
||||
$passwordInputParent.find('.js-password-strength__indicator')
|
||||
.css('width', `${result.strength}%`)
|
||||
.removeClass('is-weak is-fair is-good is-strong')
|
||||
.addClass(result.indicatorClass);
|
||||
|
||||
// Update the strength indication text.
|
||||
$passwordInputParent.find('.js-password-strength__text').html(result.indicatorText);
|
||||
}
|
||||
|
||||
// Check the value in the confirm input and show results.
|
||||
if ($confirmInput.val()) {
|
||||
passwordCheckMatch($confirmInput.val());
|
||||
$confirmResult.css({ visibility: 'visible' });
|
||||
}
|
||||
else {
|
||||
$confirmResult.css({ visibility: 'hidden' });
|
||||
}
|
||||
};
|
||||
|
||||
// Monitor input events.
|
||||
$passwordInput.on('input', passwordCheck);
|
||||
$confirmInput.on('input', passwordCheck);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate the strength of a user's password.
|
||||
*
|
||||
* Returns the estimated strength and the relevant output message.
|
||||
*
|
||||
* @param {string} password
|
||||
* The password to evaluate.
|
||||
* @param {object} translate
|
||||
* An object containing the text to display for each strength level.
|
||||
*
|
||||
* @return {object}
|
||||
* An object containing strength, message, indicatorText and indicatorClass.
|
||||
*/
|
||||
Drupal.evaluatePasswordStrength = function (password, translate) {
|
||||
password = password.trim();
|
||||
let indicatorText;
|
||||
let indicatorClass;
|
||||
let weaknesses = 0;
|
||||
let strength = 100;
|
||||
let msg = [];
|
||||
|
||||
const hasLowercase = /[a-z]/.test(password);
|
||||
const hasUppercase = /[A-Z]/.test(password);
|
||||
const hasNumbers = /[0-9]/.test(password);
|
||||
const hasPunctuation = /[^a-zA-Z0-9]/.test(password);
|
||||
|
||||
// If there is a username edit box on the page, compare password to that,
|
||||
// otherwise use value from the database.
|
||||
const $usernameBox = $('input.username');
|
||||
const username = ($usernameBox.length > 0) ? $usernameBox.val() : translate.username;
|
||||
|
||||
// Lose 5 points for every character less than 12, plus a 30 point penalty.
|
||||
if (password.length < 12) {
|
||||
msg.push(translate.tooShort);
|
||||
strength -= ((12 - password.length) * 5) + 30;
|
||||
}
|
||||
|
||||
// Count weaknesses.
|
||||
if (!hasLowercase) {
|
||||
msg.push(translate.addLowerCase);
|
||||
weaknesses++;
|
||||
}
|
||||
if (!hasUppercase) {
|
||||
msg.push(translate.addUpperCase);
|
||||
weaknesses++;
|
||||
}
|
||||
if (!hasNumbers) {
|
||||
msg.push(translate.addNumbers);
|
||||
weaknesses++;
|
||||
}
|
||||
if (!hasPunctuation) {
|
||||
msg.push(translate.addPunctuation);
|
||||
weaknesses++;
|
||||
}
|
||||
|
||||
// Apply penalty for each weakness (balanced against length penalty).
|
||||
switch (weaknesses) {
|
||||
case 1:
|
||||
strength -= 12.5;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
strength -= 25;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
strength -= 40;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
strength -= 40;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if password is the same as the username.
|
||||
if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
|
||||
msg.push(translate.sameAsUsername);
|
||||
// Passwords the same as username are always very weak.
|
||||
strength = 5;
|
||||
}
|
||||
|
||||
// Based on the strength, work out what text should be shown by the
|
||||
// password strength meter.
|
||||
if (strength < 60) {
|
||||
indicatorText = translate.weak;
|
||||
indicatorClass = 'is-weak';
|
||||
}
|
||||
else if (strength < 70) {
|
||||
indicatorText = translate.fair;
|
||||
indicatorClass = 'is-fair';
|
||||
}
|
||||
else if (strength < 80) {
|
||||
indicatorText = translate.good;
|
||||
indicatorClass = 'is-good';
|
||||
}
|
||||
else if (strength <= 100) {
|
||||
indicatorText = translate.strong;
|
||||
indicatorClass = 'is-strong';
|
||||
}
|
||||
|
||||
// Assemble the final message.
|
||||
msg = `${translate.hasWeaknesses}<ul><li>${msg.join('</li><li>')}</li></ul>`;
|
||||
|
||||
return {
|
||||
strength,
|
||||
message: msg,
|
||||
indicatorText,
|
||||
indicatorClass,
|
||||
};
|
||||
};
|
||||
}(jQuery, Drupal, drupalSettings));
|
||||
@@ -9,8 +9,8 @@ configure: user.admin_index
|
||||
dependencies:
|
||||
- system
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
+24
-85
@@ -1,24 +1,13 @@
|
||||
/**
|
||||
* @file
|
||||
* User behaviors.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal, drupalSettings) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Attach handlers to evaluate the strength of any password fields and to
|
||||
* check that its confirmation is correct.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches password strength indicator and other relevant validation to
|
||||
* password fields.
|
||||
*/
|
||||
Drupal.behaviors.password = {
|
||||
attach: function (context, settings) {
|
||||
attach: function attach(context, settings) {
|
||||
var $passwordInput = $(context).find('input.js-password-field').once('password');
|
||||
|
||||
if ($passwordInput.length) {
|
||||
@@ -26,23 +15,16 @@
|
||||
|
||||
var $passwordInputParent = $passwordInput.parent();
|
||||
var $passwordInputParentWrapper = $passwordInputParent.parent();
|
||||
var $passwordSuggestions;
|
||||
var $passwordSuggestions = void 0;
|
||||
|
||||
// Add identifying class to password element parent.
|
||||
$passwordInputParent.addClass('password-parent');
|
||||
|
||||
// Add the password confirmation layer.
|
||||
$passwordInputParentWrapper
|
||||
.find('input.js-password-confirm')
|
||||
.parent()
|
||||
.append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>')
|
||||
.addClass('confirm-parent');
|
||||
$passwordInputParentWrapper.find('input.js-password-confirm').parent().append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>').addClass('confirm-parent');
|
||||
|
||||
var $confirmInput = $passwordInputParentWrapper.find('input.js-password-confirm');
|
||||
var $confirmResult = $passwordInputParentWrapper.find('div.js-password-confirm');
|
||||
var $confirmChild = $confirmResult.find('span');
|
||||
|
||||
// If the password strength indicator is enabled, add its markup.
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
var passwordMeter = '<div class="password-strength"><div class="password-strength__meter"><div class="password-strength__indicator js-password-strength__indicator"></div></div><div aria-live="polite" aria-atomic="true" class="password-strength__title">' + translate.strengthTitle + ' <span class="password-strength__text js-password-strength__text"></span></div></div>';
|
||||
$confirmInput.parent().after('<div class="password-suggestions description"></div>');
|
||||
@@ -50,75 +32,46 @@
|
||||
$passwordSuggestions = $passwordInputParentWrapper.find('div.password-suggestions').hide();
|
||||
}
|
||||
|
||||
// Check that password and confirmation inputs match.
|
||||
var passwordCheckMatch = function (confirmInputVal) {
|
||||
var passwordCheckMatch = function passwordCheckMatch(confirmInputVal) {
|
||||
var success = $passwordInput.val() === confirmInputVal;
|
||||
var confirmClass = success ? 'ok' : 'error';
|
||||
|
||||
// Fill in the success message and set the class accordingly.
|
||||
$confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
|
||||
.removeClass('ok error').addClass(confirmClass);
|
||||
$confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')]).removeClass('ok error').addClass(confirmClass);
|
||||
};
|
||||
|
||||
// Check the password strength.
|
||||
var passwordCheck = function () {
|
||||
var passwordCheck = function passwordCheck() {
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
// Evaluate the password strength.
|
||||
var result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
|
||||
|
||||
// Update the suggestions for how to improve the password.
|
||||
if ($passwordSuggestions.html() !== result.message) {
|
||||
$passwordSuggestions.html(result.message);
|
||||
}
|
||||
|
||||
// Only show the description box if a weakness exists in the
|
||||
// password.
|
||||
$passwordSuggestions.toggle(result.strength !== 100);
|
||||
|
||||
// Adjust the length of the strength indicator.
|
||||
$passwordInputParent.find('.js-password-strength__indicator')
|
||||
.css('width', result.strength + '%')
|
||||
.removeClass('is-weak is-fair is-good is-strong')
|
||||
.addClass(result.indicatorClass);
|
||||
$passwordInputParent.find('.js-password-strength__indicator').css('width', result.strength + '%').removeClass('is-weak is-fair is-good is-strong').addClass(result.indicatorClass);
|
||||
|
||||
// Update the strength indication text.
|
||||
$passwordInputParent.find('.js-password-strength__text').html(result.indicatorText);
|
||||
}
|
||||
|
||||
// Check the value in the confirm input and show results.
|
||||
if ($confirmInput.val()) {
|
||||
passwordCheckMatch($confirmInput.val());
|
||||
$confirmResult.css({visibility: 'visible'});
|
||||
}
|
||||
else {
|
||||
$confirmResult.css({visibility: 'hidden'});
|
||||
$confirmResult.css({ visibility: 'visible' });
|
||||
} else {
|
||||
$confirmResult.css({ visibility: 'hidden' });
|
||||
}
|
||||
};
|
||||
|
||||
// Monitor input events.
|
||||
$passwordInput.on('input', passwordCheck);
|
||||
$confirmInput.on('input', passwordCheck);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate the strength of a user's password.
|
||||
*
|
||||
* Returns the estimated strength and the relevant output message.
|
||||
*
|
||||
* @param {string} password
|
||||
* The password to evaluate.
|
||||
* @param {object} translate
|
||||
* An object containing the text to display for each strength level.
|
||||
*
|
||||
* @return {object}
|
||||
* An object containing strength, message, indicatorText and indicatorClass.
|
||||
*/
|
||||
Drupal.evaluatePasswordStrength = function (password, translate) {
|
||||
password = password.trim();
|
||||
var indicatorText;
|
||||
var indicatorClass;
|
||||
var indicatorText = void 0;
|
||||
var indicatorClass = void 0;
|
||||
var weaknesses = 0;
|
||||
var strength = 100;
|
||||
var msg = [];
|
||||
@@ -128,18 +81,14 @@
|
||||
var hasNumbers = /[0-9]/.test(password);
|
||||
var hasPunctuation = /[^a-zA-Z0-9]/.test(password);
|
||||
|
||||
// If there is a username edit box on the page, compare password to that,
|
||||
// otherwise use value from the database.
|
||||
var $usernameBox = $('input.username');
|
||||
var username = ($usernameBox.length > 0) ? $usernameBox.val() : translate.username;
|
||||
var username = $usernameBox.length > 0 ? $usernameBox.val() : translate.username;
|
||||
|
||||
// Lose 5 points for every character less than 12, plus a 30 point penalty.
|
||||
if (password.length < 12) {
|
||||
msg.push(translate.tooShort);
|
||||
strength -= ((12 - password.length) * 5) + 30;
|
||||
strength -= (12 - password.length) * 5 + 30;
|
||||
}
|
||||
|
||||
// Count weaknesses.
|
||||
if (!hasLowercase) {
|
||||
msg.push(translate.addLowerCase);
|
||||
weaknesses++;
|
||||
@@ -157,7 +106,6 @@
|
||||
weaknesses++;
|
||||
}
|
||||
|
||||
// Apply penalty for each weakness (balanced against length penalty).
|
||||
switch (weaknesses) {
|
||||
case 1:
|
||||
strength -= 12.5;
|
||||
@@ -176,33 +124,26 @@
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if password is the same as the username.
|
||||
if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
|
||||
msg.push(translate.sameAsUsername);
|
||||
// Passwords the same as username are always very weak.
|
||||
|
||||
strength = 5;
|
||||
}
|
||||
|
||||
// Based on the strength, work out what text should be shown by the
|
||||
// password strength meter.
|
||||
if (strength < 60) {
|
||||
indicatorText = translate.weak;
|
||||
indicatorClass = 'is-weak';
|
||||
}
|
||||
else if (strength < 70) {
|
||||
} else if (strength < 70) {
|
||||
indicatorText = translate.fair;
|
||||
indicatorClass = 'is-fair';
|
||||
}
|
||||
else if (strength < 80) {
|
||||
} else if (strength < 80) {
|
||||
indicatorText = translate.good;
|
||||
indicatorClass = 'is-good';
|
||||
}
|
||||
else if (strength <= 100) {
|
||||
} else if (strength <= 100) {
|
||||
indicatorText = translate.strong;
|
||||
indicatorClass = 'is-strong';
|
||||
}
|
||||
|
||||
// Assemble the final message.
|
||||
msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
|
||||
|
||||
return {
|
||||
@@ -211,7 +152,5 @@
|
||||
indicatorText: indicatorText,
|
||||
indicatorClass: indicatorClass
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, drupalSettings);
|
||||
})(jQuery, Drupal, drupalSettings);
|
||||
@@ -31,6 +31,8 @@ use Drupal\user\UserInterface;
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\user\UserInterface::USERNAME_MAX_LENGTH instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2831620
|
||||
*/
|
||||
const USERNAME_MAX_LENGTH = 60;
|
||||
|
||||
@@ -39,6 +41,8 @@ const USERNAME_MAX_LENGTH = 60;
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\user\UserInterface::REGISTER_ADMINISTRATORS_ONLY instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2831620
|
||||
*/
|
||||
const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
|
||||
|
||||
@@ -47,6 +51,8 @@ const USER_REGISTER_ADMINISTRATORS_ONLY = 'admin_only';
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\user\UserInterface::REGISTER_VISITORS instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2831620
|
||||
*/
|
||||
const USER_REGISTER_VISITORS = 'visitors';
|
||||
|
||||
@@ -57,6 +63,8 @@ const USER_REGISTER_VISITORS = 'visitors';
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\user\UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2831620
|
||||
*/
|
||||
const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 'visitors_admin_approval';
|
||||
|
||||
@@ -78,7 +86,7 @@ function user_help($route_name, RouteMatchInterface $route_match) {
|
||||
$output .= '<dt>' . t('Setting permissions') . '</dt>';
|
||||
$output .= '<dd>' . t('After creating roles, you can set permissions for each role on the <a href=":permissions_user">Permissions page</a>. Granting a permission allows users who have been assigned a particular role to perform an action on the site, such as viewing content, editing or creating a particular type of content, administering settings for a particular module, or using a particular function of the site (such as search).', [':permissions_user' => \Drupal::url('user.admin_permissions')]) . '</dd>';
|
||||
$output .= '<dt>' . t('Managing account settings') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":accounts">Account settings page</a> allows you to manage settings for the displayed name of the Anonymous user role, personal contact forms, user registration settings, and account cancellation settings. On this page you can also manage settings for account personalization, and adapt the text for the email messages that users receive when they register or request a password recovery. You may also set which role is automatically assigned new permissions whenever a module is enabled (the Administrator role).', [':accounts' => \Drupal::url('entity.user.admin_form')]) . '</dd>';
|
||||
$output .= '<dd>' . t('The <a href=":accounts">Account settings page</a> allows you to manage settings for the displayed name of the Anonymous user role, personal contact forms, user registration settings, and account cancellation settings. On this page you can also manage settings for account personalization, and adapt the text for the email messages that users receive when they register or request a password recovery. You may also set which role is automatically assigned new permissions whenever a module is enabled (the Administrator role).', [':accounts' => \Drupal::url('entity.user.admin_form')]) . '</dd>';
|
||||
$output .= '<dt>' . t('Managing user account fields') . '</dt>';
|
||||
$output .= '<dd>' . t('Because User accounts are an entity type, you can extend them by adding fields through the Manage fields tab on the <a href=":accounts">Account settings page</a>. By adding fields for e.g., a picture, a biography, or address, you can a create a custom profile for the users of the website. For background information on entities and fields, see the <a href=":field_help">Field module help page</a>.', [':field_help' => (\Drupal::moduleHandler()->moduleExists('field')) ? \Drupal::url('help.page', ['name' => 'field']) : '#', ':accounts' => \Drupal::url('entity.user.admin_form')]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
@@ -169,7 +177,7 @@ function user_entity_extra_field_info() {
|
||||
|
||||
$fields['user']['user']['display']['member_for'] = [
|
||||
'label' => t('Member for'),
|
||||
'description' => t('User module \'member for\' view element.'),
|
||||
'description' => t("User module 'member for' view element."),
|
||||
'weight' => 5,
|
||||
];
|
||||
|
||||
@@ -1003,7 +1011,7 @@ function user_user_role_insert(RoleInterface $role) {
|
||||
$action = Action::create([
|
||||
'id' => $add_id,
|
||||
'type' => 'user',
|
||||
'label' => t('Add the @label role to the selected users', ['@label' => $role->label()]),
|
||||
'label' => t('Add the @label role to the selected user(s)', ['@label' => $role->label()]),
|
||||
'configuration' => [
|
||||
'rid' => $role->id(),
|
||||
],
|
||||
@@ -1016,7 +1024,7 @@ function user_user_role_insert(RoleInterface $role) {
|
||||
$action = Action::create([
|
||||
'id' => $remove_id,
|
||||
'type' => 'user',
|
||||
'label' => t('Remove the @label role from the selected users', ['@label' => $role->label()]),
|
||||
'label' => t('Remove the @label role from the selected user(s)', ['@label' => $role->label()]),
|
||||
'configuration' => [
|
||||
'rid' => $role->id(),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @file
|
||||
* User permission page behaviors.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
/**
|
||||
* Shows checked and disabled checkboxes for inherited permissions.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches functionality to the permissions table.
|
||||
*/
|
||||
Drupal.behaviors.permissions = {
|
||||
attach(context) {
|
||||
const self = this;
|
||||
$('table#permissions').once('permissions').each(function () {
|
||||
// On a site with many roles and permissions, this behavior initially
|
||||
// has to perform thousands of DOM manipulations to inject checkboxes
|
||||
// and hide them. By detaching the table from the DOM, all operations
|
||||
// can be performed without triggering internal layout and re-rendering
|
||||
// processes in the browser.
|
||||
const $table = $(this);
|
||||
let $ancestor;
|
||||
let method;
|
||||
if ($table.prev().length) {
|
||||
$ancestor = $table.prev();
|
||||
method = 'after';
|
||||
}
|
||||
else {
|
||||
$ancestor = $table.parent();
|
||||
method = 'append';
|
||||
}
|
||||
$table.detach();
|
||||
|
||||
// Create dummy checkboxes. We use dummy checkboxes instead of reusing
|
||||
// the existing checkboxes here because new checkboxes don't alter the
|
||||
// submitted form. If we'd automatically check existing checkboxes, the
|
||||
// permission table would be polluted with redundant entries. This
|
||||
// is deliberate, but desirable when we automatically check them.
|
||||
const $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />')
|
||||
.attr('title', Drupal.t('This permission is inherited from the authenticated user role.'))
|
||||
.hide();
|
||||
|
||||
$table
|
||||
.find('input[type="checkbox"]')
|
||||
.not('.js-rid-anonymous, .js-rid-authenticated')
|
||||
.addClass('real-checkbox js-real-checkbox')
|
||||
.after($dummy);
|
||||
|
||||
// Initialize the authenticated user checkbox.
|
||||
$table.find('input[type=checkbox].js-rid-authenticated')
|
||||
.on('click.permissions', self.toggle)
|
||||
// .triggerHandler() cannot be used here, as it only affects the first
|
||||
// element.
|
||||
.each(self.toggle);
|
||||
|
||||
// Re-insert the table into the DOM.
|
||||
$ancestor[method]($table);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles all dummy checkboxes based on the checkboxes' state.
|
||||
*
|
||||
* If the "authenticated user" checkbox is checked, the checked and disabled
|
||||
* checkboxes are shown, the real checkboxes otherwise.
|
||||
*/
|
||||
toggle() {
|
||||
const authCheckbox = this;
|
||||
const $row = $(this).closest('tr');
|
||||
// jQuery performs too many layout calculations for .hide() and .show(),
|
||||
// leading to a major page rendering lag on sites with many roles and
|
||||
// permissions. Therefore, we toggle visibility directly.
|
||||
$row.find('.js-real-checkbox').each(function () {
|
||||
this.style.display = (authCheckbox.checked ? 'none' : '');
|
||||
});
|
||||
$row.find('.js-dummy-checkbox').each(function () {
|
||||
this.style.display = (authCheckbox.checked ? '' : 'none');
|
||||
});
|
||||
},
|
||||
};
|
||||
}(jQuery, Drupal));
|
||||
@@ -1,88 +1,46 @@
|
||||
/**
|
||||
* @file
|
||||
* User permission page behaviors.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Shows checked and disabled checkboxes for inherited permissions.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches functionality to the permissions table.
|
||||
*/
|
||||
Drupal.behaviors.permissions = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
var self = this;
|
||||
$('table#permissions').once('permissions').each(function () {
|
||||
// On a site with many roles and permissions, this behavior initially
|
||||
// has to perform thousands of DOM manipulations to inject checkboxes
|
||||
// and hide them. By detaching the table from the DOM, all operations
|
||||
// can be performed without triggering internal layout and re-rendering
|
||||
// processes in the browser.
|
||||
var $table = $(this);
|
||||
var $ancestor;
|
||||
var method;
|
||||
var $ancestor = void 0;
|
||||
var method = void 0;
|
||||
if ($table.prev().length) {
|
||||
$ancestor = $table.prev();
|
||||
method = 'after';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$ancestor = $table.parent();
|
||||
method = 'append';
|
||||
}
|
||||
$table.detach();
|
||||
|
||||
// Create dummy checkboxes. We use dummy checkboxes instead of reusing
|
||||
// the existing checkboxes here because new checkboxes don't alter the
|
||||
// submitted form. If we'd automatically check existing checkboxes, the
|
||||
// permission table would be polluted with redundant entries. This
|
||||
// is deliberate, but desirable when we automatically check them.
|
||||
var $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />')
|
||||
.attr('title', Drupal.t('This permission is inherited from the authenticated user role.'))
|
||||
.hide();
|
||||
var $dummy = $('<input type="checkbox" class="dummy-checkbox js-dummy-checkbox" disabled="disabled" checked="checked" />').attr('title', Drupal.t('This permission is inherited from the authenticated user role.')).hide();
|
||||
|
||||
$table
|
||||
.find('input[type="checkbox"]')
|
||||
.not('.js-rid-anonymous, .js-rid-authenticated')
|
||||
.addClass('real-checkbox js-real-checkbox')
|
||||
.after($dummy);
|
||||
$table.find('input[type="checkbox"]').not('.js-rid-anonymous, .js-rid-authenticated').addClass('real-checkbox js-real-checkbox').after($dummy);
|
||||
|
||||
// Initialize the authenticated user checkbox.
|
||||
$table.find('input[type=checkbox].js-rid-authenticated')
|
||||
.on('click.permissions', self.toggle)
|
||||
// .triggerHandler() cannot be used here, as it only affects the first
|
||||
// element.
|
||||
.each(self.toggle);
|
||||
$table.find('input[type=checkbox].js-rid-authenticated').on('click.permissions', self.toggle).each(self.toggle);
|
||||
|
||||
// Re-insert the table into the DOM.
|
||||
$ancestor[method]($table);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles all dummy checkboxes based on the checkboxes' state.
|
||||
*
|
||||
* If the "authenticated user" checkbox is checked, the checked and disabled
|
||||
* checkboxes are shown, the real checkboxes otherwise.
|
||||
*/
|
||||
toggle: function () {
|
||||
toggle: function toggle() {
|
||||
var authCheckbox = this;
|
||||
var $row = $(this).closest('tr');
|
||||
// jQuery performs too many layout calculations for .hide() and .show(),
|
||||
// leading to a major page rendering lag on sites with many roles and
|
||||
// permissions. Therefore, we toggle visibility directly.
|
||||
|
||||
$row.find('.js-real-checkbox').each(function () {
|
||||
this.style.display = (authCheckbox.checked ? 'none' : '');
|
||||
this.style.display = authCheckbox.checked ? 'none' : '';
|
||||
});
|
||||
$row.find('.js-dummy-checkbox').each(function () {
|
||||
this.style.display = (authCheckbox.checked ? '' : 'none');
|
||||
this.style.display = authCheckbox.checked ? '' : 'none';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, Drupal);
|
||||
})(jQuery, Drupal);
|
||||
@@ -111,6 +111,15 @@ user.pass:
|
||||
options:
|
||||
_maintenance_access: TRUE
|
||||
|
||||
user.pass.http:
|
||||
path: '/user/password'
|
||||
defaults:
|
||||
_controller: \Drupal\user\Controller\UserAuthenticationController::resetPassword
|
||||
methods: [POST]
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
_format: 'json'
|
||||
|
||||
user.page:
|
||||
path: '/user'
|
||||
defaults:
|
||||
|
||||
Reference in New Issue
Block a user