Initial Drupal 11 with DDEV setup
This commit is contained in:
36
web/core/modules/user/src/Access/LoginStatusCheck.php
Normal file
36
web/core/modules/user/src/Access/LoginStatusCheck.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Determines access to routes based on login status of current user.
|
||||
*/
|
||||
class LoginStatusCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(AccountInterface $account, Route $route) {
|
||||
$required_status = filter_var($route->getRequirement('_user_is_logged_in'), FILTER_VALIDATE_BOOLEAN);
|
||||
$actual_status = $account->isAuthenticated();
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
||||
47
web/core/modules/user/src/Access/PermissionAccessCheck.php
Normal file
47
web/core/modules/user/src/Access/PermissionAccessCheck.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Permission access check.
|
||||
*
|
||||
* Determines access to routes based on permissions defined via
|
||||
* $module.permissions.yml files.
|
||||
*/
|
||||
class PermissionAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, AccountInterface $account) {
|
||||
$permission = $route->getRequirement('_permission');
|
||||
|
||||
if ($permission === NULL) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
// Allow to conjunct the permissions with OR ('+') or AND (',').
|
||||
$split = explode(',', $permission);
|
||||
if (count($split) > 1) {
|
||||
return AccessResult::allowedIfHasPermissions($account, $split, 'AND');
|
||||
}
|
||||
else {
|
||||
$split = explode('+', $permission);
|
||||
return AccessResult::allowedIfHasPermissions($account, $split, 'OR');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
29
web/core/modules/user/src/Access/RegisterAccessCheck.php
Normal file
29
web/core/modules/user/src/Access/RegisterAccessCheck.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Access check for user registration routes.
|
||||
*/
|
||||
class RegisterAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
$user_settings = \Drupal::config('user.settings');
|
||||
return AccessResult::allowedIf($account->isAnonymous() && $user_settings->get('register') != UserInterface::REGISTER_ADMINISTRATORS_ONLY)->addCacheableDependency($user_settings);
|
||||
}
|
||||
|
||||
}
|
||||
53
web/core/modules/user/src/Access/RoleAccessCheck.php
Normal file
53
web/core/modules/user/src/Access/RoleAccessCheck.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Determines access to routes based on roles.
|
||||
*
|
||||
* You can specify the '_role' key on route requirements. If you specify a
|
||||
* single role, users with that role with have access. If you specify multiple
|
||||
* ones you can conjunct them with AND by using a "," and with OR by using "+".
|
||||
*/
|
||||
class RoleAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, AccountInterface $account) {
|
||||
// Requirements just allow strings, so this might be a comma separated list.
|
||||
$rid_string = $route->getRequirement('_role');
|
||||
|
||||
$explode_and = array_filter(array_map('trim', explode(',', $rid_string)));
|
||||
if (count($explode_and) > 1) {
|
||||
$diff = array_diff($explode_and, $account->getRoles());
|
||||
if (empty($diff)) {
|
||||
return AccessResult::allowed()->addCacheContexts(['user.roles']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$explode_or = array_filter(array_map('trim', explode('+', $rid_string)));
|
||||
$intersection = array_intersect($explode_or, $account->getRoles());
|
||||
if (!empty($intersection)) {
|
||||
return AccessResult::allowed()->addCacheContexts(['user.roles']);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no allowed role, give other access checks a chance.
|
||||
return AccessResult::neutral()->addCacheContexts(['user.roles']);
|
||||
}
|
||||
|
||||
}
|
||||
449
web/core/modules/user/src/AccountForm.php
Normal file
449
web/core/modules/user/src/AccountForm.php
Normal file
@ -0,0 +1,449 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Datetime\TimeZoneFormHelper;
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Security\TrustedCallbackInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\ConfigurableLanguageManagerInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
|
||||
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form controller for the user account forms.
|
||||
*/
|
||||
abstract class AccountForm extends ContentEntityForm implements TrustedCallbackInterface {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The entity type bundle service.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(EntityRepositoryInterface $entity_repository, LanguageManagerInterface $language_manager, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, ?TimeInterface $time = NULL) {
|
||||
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.repository'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('datetime.time')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $this->entity;
|
||||
$user = $this->currentUser();
|
||||
$config = \Drupal::config('user.settings');
|
||||
$form['#cache']['tags'] = $config->getCacheTags();
|
||||
|
||||
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
|
||||
|
||||
// Check for new account.
|
||||
$register = $account->isNew();
|
||||
|
||||
// For a new account, there are 2 sub-cases:
|
||||
// $self_register: A user creates their own, new, account
|
||||
// (path '/user/register')
|
||||
// $admin_create: An administrator creates a new account for another user
|
||||
// (path '/admin/people/create')
|
||||
// If the current user is logged in and has permission to create users
|
||||
// then it must be the second case.
|
||||
$admin_create = $register && $account->access('create');
|
||||
$self_register = $register && !$admin_create;
|
||||
|
||||
// Account information.
|
||||
$form['account'] = [
|
||||
'#type' => 'container',
|
||||
'#weight' => -10,
|
||||
];
|
||||
|
||||
// The mail field is NOT required if account originally had no mail set
|
||||
// and the user performing the edit has 'administer users' permission.
|
||||
// This allows users without email address to be edited and deleted.
|
||||
// Also see \Drupal\user\Plugin\Validation\Constraint\UserMailRequired.
|
||||
$form['account']['mail'] = [
|
||||
'#type' => 'email',
|
||||
'#title' => $this->t('Email address'),
|
||||
'#description' => $this->t('The email address is not made public. It will only be used if you need to be contacted about your account or for opted-in notifications.'),
|
||||
'#required' => !(!$account->getEmail() && $user->hasPermission('administer users')),
|
||||
'#default_value' => (!$register ? $account->getEmail() : ''),
|
||||
'#access' => $account->mail->access('edit'),
|
||||
];
|
||||
|
||||
// Only show name field on registration form or user can change own
|
||||
// username.
|
||||
$form['account']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Username'),
|
||||
'#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
|
||||
'#description' => $this->t("Several special characters are allowed, including space, period (.), hyphen (-), apostrophe ('), underscore (_), and the @ sign."),
|
||||
'#required' => TRUE,
|
||||
'#attributes' => [
|
||||
'class' => ['username'],
|
||||
'autocorrect' => 'off',
|
||||
'autocapitalize' => 'off',
|
||||
'spellcheck' => 'false',
|
||||
],
|
||||
'#default_value' => (!$register ? $account->getAccountName() : ''),
|
||||
'#access' => $account->name->access('edit'),
|
||||
];
|
||||
|
||||
// Display password field only for existing users or when user is allowed to
|
||||
// assign a password during registration.
|
||||
if (!$register) {
|
||||
$form['account']['pass'] = [
|
||||
'#type' => 'password_confirm',
|
||||
'#size' => 25,
|
||||
'#description' => $this->t('To change the current user password, enter the new password in both fields.'),
|
||||
];
|
||||
|
||||
// To skip the current password field, the user must have logged in via a
|
||||
// one-time link and have the token in the URL. Store this in $form_state
|
||||
// so it persists even on subsequent Ajax requests.
|
||||
$request = $this->getRequest();
|
||||
if (!$form_state->get('user_pass_reset') && ($token = $request->query->get('pass-reset-token'))) {
|
||||
$session_key = 'pass_reset_' . $account->id();
|
||||
$session_value = $request->getSession()->get($session_key);
|
||||
$user_pass_reset = isset($session_value) && hash_equals($session_value, $token);
|
||||
$form_state->set('user_pass_reset', $user_pass_reset);
|
||||
}
|
||||
|
||||
// The user must enter their current password to change to a new one.
|
||||
if ($user->id() == $account->id()) {
|
||||
$form['account']['current_pass'] = [
|
||||
'#type' => 'password',
|
||||
'#title' => $this->t('Current password'),
|
||||
'#size' => 25,
|
||||
'#access' => !$form_state->get('user_pass_reset'),
|
||||
'#weight' => -5,
|
||||
// Do not let web browsers remember this password, since we are
|
||||
// trying to confirm that the person submitting the form actually
|
||||
// knows the current one.
|
||||
'#attributes' => ['autocomplete' => 'off'],
|
||||
];
|
||||
$form_state->set('user', $account);
|
||||
|
||||
// If logged in via a one-time login link entering a new password is
|
||||
// required and the user does not need to enter their current password.
|
||||
if ($form_state->get('user_pass_reset')) {
|
||||
$form['account']['pass']['#required'] = TRUE;
|
||||
}
|
||||
else {
|
||||
$form['account']['current_pass']['#description'] = $this->t('Required if you want to change the <em>Email address</em> or the <em>Password</em> field below. <a href=":request_new_url" title="Send password reset instructions via email.">Reset your password</a>.', [
|
||||
':request_new_url' => Url::fromRoute('user.pass')->toString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!$config->get('verify_mail') || $admin_create) {
|
||||
$form['account']['pass'] = [
|
||||
'#type' => 'password_confirm',
|
||||
'#size' => 25,
|
||||
'#description' => $this->t('Provide a password for the new account in both fields.'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
// When not building the user registration form, prevent web browsers from
|
||||
// auto-filling/prefilling the email, username, and password fields.
|
||||
if (!$register) {
|
||||
foreach (['mail', 'name', 'pass'] as $key) {
|
||||
if (isset($form['account'][$key])) {
|
||||
$form['account'][$key]['#attributes']['autocomplete'] = 'off';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$self_register) {
|
||||
$status = $account->get('status')->value;
|
||||
}
|
||||
else {
|
||||
$status = $config->get('register') == UserInterface::REGISTER_VISITORS ? 1 : 0;
|
||||
}
|
||||
|
||||
$form['account']['status'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Status'),
|
||||
'#default_value' => $status,
|
||||
'#options' => [$this->t('Blocked'), $this->t('Active')],
|
||||
'#access' => $account->status->access('edit') && $user->id() !== $account->id(),
|
||||
];
|
||||
|
||||
$roles = Role::loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
$roles = array_map(fn(RoleInterface $role) => Html::escape($role->label()), $roles);
|
||||
|
||||
$form['account']['roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Roles'),
|
||||
'#default_value' => (!$register ? $account->getRoles() : []),
|
||||
'#options' => $roles,
|
||||
'#access' => $roles && $user->hasPermission('administer permissions'),
|
||||
];
|
||||
|
||||
// Special handling for the inevitable "Authenticated user" role.
|
||||
$form['account']['roles'][RoleInterface::AUTHENTICATED_ID] = [
|
||||
'#default_value' => TRUE,
|
||||
'#disabled' => TRUE,
|
||||
];
|
||||
|
||||
$form['account']['notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user of new account'),
|
||||
'#access' => $admin_create,
|
||||
];
|
||||
|
||||
$user_preferred_langcode = $register ? $language_interface->getId() : $account->getPreferredLangcode();
|
||||
|
||||
$user_preferred_admin_langcode = $register ? $language_interface->getId() : $account->getPreferredAdminLangcode(FALSE);
|
||||
|
||||
// Is the user preferred language added?
|
||||
$user_language_added = FALSE;
|
||||
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
|
||||
$negotiator = $this->languageManager->getNegotiator();
|
||||
$user_language_added = $negotiator && $negotiator->isNegotiationMethodEnabled(LanguageNegotiationUser::METHOD_ID, LanguageInterface::TYPE_INTERFACE);
|
||||
}
|
||||
$form['language'] = [
|
||||
'#type' => $this->languageManager->isMultilingual() ? 'details' : 'container',
|
||||
'#title' => $this->t('Language settings'),
|
||||
'#open' => TRUE,
|
||||
// Display language selector when either creating a user on the admin
|
||||
// interface or editing a user account.
|
||||
'#access' => !$self_register,
|
||||
];
|
||||
|
||||
$form['language']['preferred_langcode'] = [
|
||||
'#type' => 'language_select',
|
||||
'#title' => $this->t('Site language'),
|
||||
'#languages' => LanguageInterface::STATE_CONFIGURABLE,
|
||||
'#default_value' => $user_preferred_langcode,
|
||||
'#description' => $user_language_added ? $this->t("This account's preferred language for emails and site presentation.") : $this->t("This account's preferred language for emails."),
|
||||
// This is used to explain that user preferred language and entity
|
||||
// language are synchronized. It can be removed if a different behavior is
|
||||
// desired.
|
||||
'#pre_render' => ['user_langcode' => [$this, 'alterPreferredLangcodeDescription']],
|
||||
];
|
||||
|
||||
// Only show the account setting for Administration pages language to users
|
||||
// if one of the detection and selection methods uses it.
|
||||
$show_admin_language = FALSE;
|
||||
if (($account->hasPermission('access administration pages') || $account->hasPermission('view the administration theme')) && $this->languageManager instanceof ConfigurableLanguageManagerInterface) {
|
||||
$negotiator = $this->languageManager->getNegotiator();
|
||||
$show_admin_language = $negotiator && $negotiator->isNegotiationMethodEnabled(LanguageNegotiationUserAdmin::METHOD_ID);
|
||||
}
|
||||
$form['language']['preferred_admin_langcode'] = [
|
||||
'#type' => 'language_select',
|
||||
'#title' => $this->t('Administration pages language'),
|
||||
'#languages' => LanguageInterface::STATE_CONFIGURABLE,
|
||||
'#default_value' => $user_preferred_admin_langcode,
|
||||
'#access' => $show_admin_language,
|
||||
'#empty_option' => $this->t('- No preference -'),
|
||||
'#empty_value' => '',
|
||||
];
|
||||
|
||||
// User entities contain both a langcode property (for identifying the
|
||||
// language of the entity data) and a preferred_langcode property (see
|
||||
// above). Rather than provide a UI forcing the user to choose both
|
||||
// separately, assume that the user profile data is in the user's preferred
|
||||
// language. This entity builder provides that synchronization. For
|
||||
// use-cases where this synchronization is not desired, a module can alter
|
||||
// or remove this item. Sync user langcode only when a user registers and
|
||||
// not when a user is updated or translated.
|
||||
if ($register) {
|
||||
$form['#entity_builders']['sync_user_langcode'] = '::syncUserLangcode';
|
||||
}
|
||||
|
||||
$system_date_config = \Drupal::config('system.date');
|
||||
$form['timezone'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Locale settings'),
|
||||
'#open' => TRUE,
|
||||
'#weight' => 6,
|
||||
'#access' => $system_date_config->get('timezone.user.configurable'),
|
||||
];
|
||||
if ($self_register && $system_date_config->get('timezone.user.default') != UserInterface::TIMEZONE_SELECT) {
|
||||
$form['timezone']['#access'] = FALSE;
|
||||
}
|
||||
$form['timezone']['timezone'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Time zone'),
|
||||
'#default_value' => $account->getTimezone() ?: $system_date_config->get('timezone.default'),
|
||||
'#options' => TimeZoneFormHelper::getOptionsListByRegion($account->id() != $user->id()),
|
||||
'#description' => $this->t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
|
||||
];
|
||||
|
||||
// If not set or selected yet, detect timezone for the current user only.
|
||||
$user_input = $form_state->getUserInput();
|
||||
if (!$account->getTimezone() && $account->id() == $user->id() && empty($user_input['timezone'])) {
|
||||
$form['timezone']['#attached']['library'][] = 'core/drupal.timezone';
|
||||
$form['timezone']['timezone']['#attributes'] = ['class' => ['timezone-detect']];
|
||||
}
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function trustedCallbacks() {
|
||||
return ['alterPreferredLangcodeDescription'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the preferred language widget description.
|
||||
*
|
||||
* @param array $element
|
||||
* The preferred language form element.
|
||||
*
|
||||
* @return array
|
||||
* The preferred language form element.
|
||||
*/
|
||||
public function alterPreferredLangcodeDescription(array $element) {
|
||||
// Only add to the description if the form element has a description.
|
||||
if (isset($element['#description'])) {
|
||||
$element['#description'] .= ' ' . $this->t("This is also assumed to be the primary language of this account's profile information.");
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes preferred language and entity language.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type identifier.
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The entity updated with the submitted values.
|
||||
* @param array $form
|
||||
* The complete form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function syncUserLangcode($entity_type_id, UserInterface $user, array &$form, FormStateInterface &$form_state) {
|
||||
$user->getUntranslated()->langcode = $user->preferred_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
// Change the roles array to a list of enabled roles.
|
||||
// @todo Alter the form state as the form values are directly extracted and
|
||||
// set on the field, which throws an exception as the list requires
|
||||
// numeric keys. Allow to override this per field. As this function is
|
||||
// called twice, we have to prevent it from getting the array keys twice.
|
||||
|
||||
if (is_string(key($form_state->getValue('roles')))) {
|
||||
$form_state->setValue('roles', array_keys(array_filter($form_state->getValue('roles'))));
|
||||
}
|
||||
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = parent::buildEntity($form, $form_state);
|
||||
|
||||
// Translate the empty value '' of language selects to an unset field.
|
||||
foreach (['preferred_langcode', 'preferred_admin_langcode'] as $field_name) {
|
||||
if ($form_state->getValue($field_name) === '') {
|
||||
$account->$field_name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Set existing password if set in the form state.
|
||||
$current_pass = trim($form_state->getValue('current_pass', ''));
|
||||
if (strlen($current_pass) > 0) {
|
||||
$account->setExistingPassword($current_pass);
|
||||
}
|
||||
|
||||
// Skip the protected user field constraint if the user came from the
|
||||
// password recovery page.
|
||||
$account->_skipProtectedUserFieldConstraint = $form_state->get('user_pass_reset');
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditedFieldNames(FormStateInterface $form_state) {
|
||||
return array_merge([
|
||||
'name',
|
||||
'pass',
|
||||
'mail',
|
||||
'roles',
|
||||
'timezone',
|
||||
'langcode',
|
||||
'preferred_langcode',
|
||||
'preferred_admin_langcode',
|
||||
], parent::getEditedFieldNames($form_state));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
|
||||
// Manually flag violations of fields not handled by the form display. This
|
||||
// is necessary as entity form displays only flag violations for fields
|
||||
// contained in the display.
|
||||
$field_names = [
|
||||
'name',
|
||||
'pass',
|
||||
'mail',
|
||||
'roles',
|
||||
'timezone',
|
||||
'langcode',
|
||||
'preferred_langcode',
|
||||
'preferred_admin_langcode',
|
||||
];
|
||||
foreach ($violations->getByFields($field_names) as $violation) {
|
||||
[$field_name] = explode('.', $violation->getPropertyPath(), 2);
|
||||
$form_state->setErrorByName($field_name, $violation->getMessage());
|
||||
}
|
||||
parent::flagViolations($violations, $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
|
||||
$user = $this->getEntity();
|
||||
// If there's a session set to the users id, remove the password reset tag
|
||||
// since a new password was saved.
|
||||
$this->getRequest()->getSession()->remove('pass_reset_' . $user->id());
|
||||
}
|
||||
|
||||
}
|
||||
421
web/core/modules/user/src/AccountSettingsForm.php
Normal file
421
web/core/modules/user/src/AccountSettingsForm.php
Normal file
@ -0,0 +1,421 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configure user settings for this site.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AccountSettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The role storage used when changing the admin role.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorageInterface
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\user\AccountSettingsForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The factory for configuration objects.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
|
||||
* The typed config manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) {
|
||||
parent::__construct($config_factory, $typedConfigManager);
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->roleStorage = $role_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('config.typed'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('entity_type.manager')->getStorage('user_role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_admin_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return [
|
||||
'system.site',
|
||||
'user.mail',
|
||||
'user.settings',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$config = $this->config('user.settings');
|
||||
$site_config = $this->config('system.site');
|
||||
|
||||
$form['#attached']['library'][] = 'user/drupal.user.admin';
|
||||
|
||||
// Settings for anonymous users.
|
||||
$form['anonymous_settings'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Anonymous users'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['anonymous_settings']['anonymous'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#config_target' => 'user.settings:anonymous',
|
||||
'#description' => $this->t('The name used to indicate anonymous users.'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
// @todo Remove this check once language settings are generalized.
|
||||
if ($this->moduleHandler->moduleExists('content_translation')) {
|
||||
$form['language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Language settings'),
|
||||
'#open' => TRUE,
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$form_state->set(['content_translation', 'key'], 'language');
|
||||
$form['language'] += content_translation_enable_widget('user', 'user', $form, $form_state);
|
||||
}
|
||||
|
||||
// User registration settings.
|
||||
$form['registration_cancellation'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Registration and cancellation'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['registration_cancellation']['user_register'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Who can register accounts?'),
|
||||
'#config_target' => 'user.settings:register',
|
||||
'#options' => [
|
||||
UserInterface::REGISTER_ADMINISTRATORS_ONLY => $this->t('Administrators only'),
|
||||
UserInterface::REGISTER_VISITORS => $this->t('Visitors'),
|
||||
UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL => $this->t('Visitors, but administrator approval is required'),
|
||||
],
|
||||
];
|
||||
$form['registration_cancellation']['user_email_verification'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Require email verification when a visitor creates an account'),
|
||||
'#config_target' => 'user.settings:verify_mail',
|
||||
'#description' => $this->t('New users will be required to validate their email address prior to logging into the site, and will be assigned a system-generated password. With this setting disabled, users will be logged in immediately upon registering, and may select their own passwords during registration.'),
|
||||
];
|
||||
$form['registration_cancellation']['user_password_strength'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enable password strength indicator'),
|
||||
'#config_target' => 'user.settings:password_strength',
|
||||
];
|
||||
$form['registration_cancellation']['user_cancel_method'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('When cancelling a user account'),
|
||||
'#config_target' => 'user.settings:cancel_method',
|
||||
'#description' => $this->t('Users with the %select-cancel-method or %administer-users <a href=":permissions-url">permissions</a> can override this default method.', ['%select-cancel-method' => $this->t('Select method for cancelling account'), '%administer-users' => $this->t('Administer users'), ':permissions-url' => Url::fromRoute('user.admin_permissions')->toString()]),
|
||||
];
|
||||
$form['registration_cancellation']['user_cancel_method'] += user_cancel_methods();
|
||||
foreach (Element::children($form['registration_cancellation']['user_cancel_method']) as $key) {
|
||||
// All account cancellation methods that specify #access cannot be
|
||||
// configured as default method.
|
||||
// @see hook_user_cancel_methods_alter()
|
||||
if (isset($form['registration_cancellation']['user_cancel_method'][$key]['#access'])) {
|
||||
$form['registration_cancellation']['user_cancel_method'][$key]['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Default notifications address.
|
||||
$form['mail_notification_address'] = [
|
||||
'#type' => 'email',
|
||||
'#title' => $this->t('Notification email address'),
|
||||
'#config_target' => 'system.site:mail_notification',
|
||||
'#description' => $this->t("The email address to be used as the 'from' address for all account notifications listed below. If <em>'Visitors, but administrator approval is required'</em> is selected above, a notification email will also be sent to this address for any new registrations. Leave empty to use the default system email address <em>(%site-email).</em>", ['%site-email' => $site_config->get('mail')]),
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
|
||||
$form['email'] = [
|
||||
'#type' => 'vertical_tabs',
|
||||
'#title' => $this->t('Emails'),
|
||||
];
|
||||
// These email tokens are shared for all settings, so just define
|
||||
// the list once to help ensure they stay in sync.
|
||||
$email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
|
||||
|
||||
$form['email_admin_created'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Welcome (new user created by administrator)'),
|
||||
'#open' => $config->get('register') == UserInterface::REGISTER_ADMINISTRATORS_ONLY,
|
||||
'#description' => $this->t('Edit the welcome email messages sent to new member accounts created by an administrator.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_admin_created']['user_mail_register_admin_created_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:register_admin_created.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_admin_created']['user_mail_register_admin_created_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:register_admin_created.body',
|
||||
'#rows' => 15,
|
||||
];
|
||||
|
||||
$form['email_pending_approval'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Welcome (awaiting approval)'),
|
||||
'#open' => $config->get('register') == UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL,
|
||||
'#description' => $this->t('Edit the welcome email messages sent to new members upon registering, when administrative approval is required.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_pending_approval']['user_mail_register_pending_approval_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:register_pending_approval.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_pending_approval']['user_mail_register_pending_approval_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:register_pending_approval.body',
|
||||
'#rows' => 8,
|
||||
];
|
||||
|
||||
$form['email_pending_approval_admin'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Admin (user awaiting approval)'),
|
||||
'#open' => $config->get('register') == UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL,
|
||||
'#description' => $this->t('Edit the email notifying the site administrator that there are new members awaiting administrative approval.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_pending_approval_admin']['register_pending_approval_admin_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:register_pending_approval_admin.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_pending_approval_admin']['register_pending_approval_admin_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:register_pending_approval_admin.body',
|
||||
'#rows' => 8,
|
||||
];
|
||||
|
||||
$form['email_no_approval_required'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Welcome (no approval required)'),
|
||||
'#open' => $config->get('register') == UserInterface::REGISTER_VISITORS,
|
||||
'#description' => $this->t('Edit the welcome email messages sent to new members upon registering, when no administrator approval is required.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_no_approval_required']['user_mail_register_no_approval_required_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:register_no_approval_required.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_no_approval_required']['user_mail_register_no_approval_required_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:register_no_approval_required.body',
|
||||
'#rows' => 15,
|
||||
];
|
||||
|
||||
$form['email_password_reset'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Password recovery'),
|
||||
'#description' => $this->t('Edit the email messages sent to users who request a new password.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
'#weight' => 10,
|
||||
];
|
||||
$form['email_password_reset']['user_mail_password_reset_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:password_reset.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_password_reset']['user_mail_password_reset_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:password_reset.body',
|
||||
'#rows' => 12,
|
||||
];
|
||||
|
||||
$form['email_activated'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Account activation'),
|
||||
'#description' => $this->t('Enable and edit email messages sent to users upon account activation (when an administrator activates an account of a user who has already registered, on a site where administrative approval is required).') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_activated']['user_mail_status_activated_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is activated'),
|
||||
'#config_target' => 'user.settings:notify.status_activated',
|
||||
];
|
||||
$form['email_activated']['settings'] = [
|
||||
'#type' => 'container',
|
||||
'#states' => [
|
||||
// Hide the additional settings when this email is disabled.
|
||||
'invisible' => [
|
||||
'input[name="user_mail_status_activated_notify"]' => ['checked' => FALSE],
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['email_activated']['settings']['user_mail_status_activated_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:status_activated.subject',
|
||||
'#states' => [
|
||||
'required' => [
|
||||
'input[name="user_mail_status_activated_notify"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_activated']['settings']['user_mail_status_activated_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:status_activated.body',
|
||||
'#rows' => 15,
|
||||
];
|
||||
|
||||
$form['email_blocked'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Account blocked'),
|
||||
'#description' => $this->t('Enable and edit email messages sent to users when their accounts are blocked.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_blocked']['user_mail_status_blocked_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is blocked'),
|
||||
'#config_target' => 'user.settings:notify.status_blocked',
|
||||
];
|
||||
$form['email_blocked']['settings'] = [
|
||||
'#type' => 'container',
|
||||
'#states' => [
|
||||
// Hide the additional settings when the blocked email is disabled.
|
||||
'invisible' => [
|
||||
'input[name="user_mail_status_blocked_notify"]' => ['checked' => FALSE],
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['email_blocked']['settings']['user_mail_status_blocked_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:status_blocked.subject',
|
||||
'#states' => [
|
||||
'required' => [
|
||||
'input[name="user_mail_status_blocked_notify"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_blocked']['settings']['user_mail_status_blocked_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:status_blocked.body',
|
||||
'#rows' => 3,
|
||||
];
|
||||
|
||||
$form['email_cancel_confirm'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Account cancellation confirmation'),
|
||||
'#description' => $this->t('Edit the email messages sent to users when they attempt to cancel their accounts.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_cancel_confirm']['user_mail_cancel_confirm_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:cancel_confirm.subject',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_cancel_confirm']['user_mail_cancel_confirm_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:cancel_confirm.body',
|
||||
'#rows' => 3,
|
||||
];
|
||||
|
||||
$form['email_canceled'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Account canceled'),
|
||||
'#description' => $this->t('Enable and edit email messages sent to users when their accounts are canceled.') . ' ' . $email_token_help,
|
||||
'#group' => 'email',
|
||||
];
|
||||
$form['email_canceled']['user_mail_status_canceled_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is canceled'),
|
||||
'#config_target' => 'user.settings:notify.status_canceled',
|
||||
];
|
||||
$form['email_canceled']['settings'] = [
|
||||
'#type' => 'container',
|
||||
'#states' => [
|
||||
// Hide the settings when the cancel notify checkbox is disabled.
|
||||
'invisible' => [
|
||||
'input[name="user_mail_status_canceled_notify"]' => ['checked' => FALSE],
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['email_canceled']['settings']['user_mail_status_canceled_subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#config_target' => 'user.mail:status_canceled.subject',
|
||||
'#states' => [
|
||||
'required' => [
|
||||
'input[name="user_mail_status_canceled_subject"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
'#maxlength' => 180,
|
||||
];
|
||||
$form['email_canceled']['settings']['user_mail_status_canceled_body'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Body'),
|
||||
'#config_target' => 'user.mail:status_canceled.body',
|
||||
'#rows' => 3,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
||||
158
web/core/modules/user/src/Authentication/Provider/Cookie.php
Normal file
158
web/core/modules/user/src/Authentication/Provider/Cookie.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Authentication\Provider;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Authentication\AuthenticationProviderInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Routing\TrustedRedirectResponse;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Session\UserSession;
|
||||
use Drupal\Core\Session\SessionConfigurationInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Cookie based authentication provider.
|
||||
*/
|
||||
class Cookie implements AuthenticationProviderInterface, EventSubscriberInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The session configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Session\SessionConfigurationInterface
|
||||
*/
|
||||
protected $sessionConfiguration;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a new cookie authentication provider.
|
||||
*
|
||||
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
|
||||
* The session configuration.
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(SessionConfigurationInterface $session_configuration, Connection $connection, MessengerInterface $messenger) {
|
||||
$this->sessionConfiguration = $session_configuration;
|
||||
$this->connection = $connection;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Request $request) {
|
||||
$applies = $this->sessionConfiguration->hasSession($request);
|
||||
if (!$applies && $request->query->has('check_logged_in')) {
|
||||
$domain = ltrim(ini_get('session.cookie_domain'), '.') ?: $request->getHttpHost();
|
||||
$this->messenger->addMessage($this->t('To log in to this site, your browser must accept cookies from the domain %domain.', ['%domain' => $domain]), 'error');
|
||||
}
|
||||
return $applies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate(Request $request) {
|
||||
return $this->getUserFromSession($request->getSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UserSession object for the given session.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
|
||||
* The session.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountInterface|null
|
||||
* The UserSession object for the current user, or NULL if this is an
|
||||
* anonymous session.
|
||||
*/
|
||||
protected function getUserFromSession(SessionInterface $session) {
|
||||
if ($uid = $session->get('uid')) {
|
||||
// @todo Load the User entity in SessionHandler so we don't need queries.
|
||||
// @see https://www.drupal.org/node/2345611
|
||||
$values = $this->connection
|
||||
->query('SELECT * FROM {users_field_data} [u] WHERE [u].[uid] = :uid AND [u].[default_langcode] = 1', [':uid' => $uid])
|
||||
->fetchAssoc();
|
||||
|
||||
// Check if the user data was found and the user is active.
|
||||
if (!empty($values) && $values['status'] == 1) {
|
||||
// Add the user's roles.
|
||||
$rids = $this->connection
|
||||
->query('SELECT [roles_target_id] FROM {user__roles} WHERE [entity_id] = :uid', [':uid' => $values['uid']])
|
||||
->fetchCol();
|
||||
$values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
|
||||
|
||||
return new UserSession($values);
|
||||
}
|
||||
}
|
||||
|
||||
// This is an anonymous session.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a query parameter to check successful log in redirect URL.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function addCheckToUrl(ResponseEvent $event) {
|
||||
$response = $event->getResponse();
|
||||
if ($response instanceof RedirectResponse) {
|
||||
if ($event->getRequest()->getSession()->has('check_logged_in')) {
|
||||
$event->getRequest()->getSession()->remove('check_logged_in');
|
||||
$url = $response->getTargetUrl();
|
||||
$options = UrlHelper::parse($url);
|
||||
$options['query']['check_logged_in'] = '1';
|
||||
$url = $options['path'] . '?' . UrlHelper::buildQuery($options['query']);
|
||||
if (!empty($options['fragment'])) {
|
||||
$url .= '#' . $options['fragment'];
|
||||
}
|
||||
// In the case of trusted redirect, we have to update the list of
|
||||
// trusted URLs because here we've just modified its target URL
|
||||
// which is in the list.
|
||||
if ($response instanceof TrustedRedirectResponse) {
|
||||
$response->setTrustedTargetUrl($url);
|
||||
}
|
||||
$response->setTargetUrl($url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[KernelEvents::RESPONSE][] = ['addCheckToUrl', -1000];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\ContextProvider;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextProviderInterface;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Sets the current user as a context.
|
||||
*/
|
||||
class CurrentUserContext implements ContextProviderInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new CurrentUserContext.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(AccountInterface $account, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->account = $account;
|
||||
$this->userStorage = $entity_type_manager->getStorage('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRuntimeContexts(array $unqualified_context_ids) {
|
||||
$current_user = $this->userStorage->load($this->account->id());
|
||||
|
||||
if ($current_user) {
|
||||
// @todo Do not validate protected fields to avoid bug in TypedData,
|
||||
// remove this in https://www.drupal.org/project/drupal/issues/2934192.
|
||||
$current_user->_skipProtectedUserFieldConstraint = TRUE;
|
||||
|
||||
$context = EntityContext::fromEntity($current_user, $this->t('Current user'));
|
||||
}
|
||||
else {
|
||||
// If not user is available, provide an empty context object.
|
||||
$context = EntityContext::fromEntityTypeId('user', $this->t('Current user'));
|
||||
}
|
||||
|
||||
$cacheability = new CacheableMetadata();
|
||||
$cacheability->setCacheContexts(['user']);
|
||||
$context->addCacheableDependency($cacheability);
|
||||
|
||||
$result = [
|
||||
'current_user' => $context,
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAvailableContexts() {
|
||||
return $this->getRuntimeContexts([]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,442 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Controller;
|
||||
|
||||
use Drupal\Core\Access\CsrfTokenGenerator;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\user\UserAuthenticationInterface;
|
||||
use Drupal\user\UserAuthInterface;
|
||||
use Drupal\user\UserFloodControlInterface;
|
||||
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;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* Provides controllers for login, login status and logout via HTTP requests.
|
||||
*/
|
||||
class UserAuthenticationController extends ControllerBase implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* String sent in responses, to describe the user as being logged in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOGGED_IN = '1';
|
||||
|
||||
/**
|
||||
* String sent in responses, to describe the user as being logged out.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOGGED_OUT = '0';
|
||||
|
||||
/**
|
||||
* The user flood control service.
|
||||
*
|
||||
* @var \Drupal\user\UserFloodControl
|
||||
*/
|
||||
protected $userFloodControl;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* The user authentication.
|
||||
*
|
||||
* @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
|
||||
*/
|
||||
protected $userAuth;
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The serializer.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* The available serialization formats.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $serializerFormats = [];
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a new UserAuthenticationController object.
|
||||
*
|
||||
* @param \Drupal\user\UserFloodControlInterface $user_flood_control
|
||||
* The user flood control service.
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
* @param \Drupal\user\UserAuthenticationInterface|\Drupal\user\UserAuthInterface $user_auth
|
||||
* The user authentication.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Symfony\Component\Serializer\Serializer $serializer
|
||||
* The serializer.
|
||||
* @param array $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthenticationInterface|UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
|
||||
$this->userFloodControl = $user_flood_control;
|
||||
$this->userStorage = $user_storage;
|
||||
$this->csrfToken = $csrf_token;
|
||||
if (!$user_auth instanceof UserAuthenticationInterface) {
|
||||
@trigger_error('The $user_auth parameter implementing UserAuthInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Implement UserAuthenticationInterface instead. See https://www.drupal.org/node/3411040');
|
||||
}
|
||||
$this->userAuth = $user_auth;
|
||||
$this->serializer = $serializer;
|
||||
$this->serializerFormats = $serializer_formats;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
if ($container->hasParameter('serializer.formats') && $container->has('serializer')) {
|
||||
$serializer = $container->get('serializer');
|
||||
$formats = $container->getParameter('serializer.formats');
|
||||
}
|
||||
else {
|
||||
$formats = ['json'];
|
||||
$encoders = [new JsonEncoder()];
|
||||
$serializer = new Serializer([], $encoders);
|
||||
}
|
||||
|
||||
return new static(
|
||||
$container->get('user.flood_control'),
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('csrf_token'),
|
||||
$container->get('user.auth'),
|
||||
$container->get('router.route_provider'),
|
||||
$serializer,
|
||||
$formats,
|
||||
$container->get('logger.factory')->get('user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* A response which contains the ID and CSRF token.
|
||||
*/
|
||||
public function login(Request $request) {
|
||||
$format = $this->getRequestFormat($request);
|
||||
|
||||
$content = $request->getContent();
|
||||
$credentials = $this->serializer->decode($content, $format);
|
||||
if (!isset($credentials['name']) && !isset($credentials['pass'])) {
|
||||
throw new BadRequestHttpException('Missing credentials.');
|
||||
}
|
||||
|
||||
if (!isset($credentials['name'])) {
|
||||
throw new BadRequestHttpException('Missing credentials.name.');
|
||||
}
|
||||
if (!isset($credentials['pass'])) {
|
||||
throw new BadRequestHttpException('Missing credentials.pass.');
|
||||
}
|
||||
|
||||
$this->floodControl($request, $credentials['name']);
|
||||
|
||||
$account = FALSE;
|
||||
|
||||
if ($this->userAuth instanceof UserAuthenticationInterface) {
|
||||
$account = $this->userAuth->lookupAccount($credentials['name']);
|
||||
}
|
||||
else {
|
||||
$accounts = $this->userStorage->loadByProperties(['name' => $credentials['name']]);
|
||||
if ($accounts) {
|
||||
$account = reset($accounts);
|
||||
}
|
||||
}
|
||||
|
||||
if ($account) {
|
||||
if ($account->isBlocked()) {
|
||||
throw new BadRequestHttpException('The user has not been activated or is blocked.');
|
||||
}
|
||||
if ($this->userAuth instanceof UserAuthenticationInterface) {
|
||||
$authenticated = $this->userAuth->authenticateAccount($account, $credentials['pass']) ? $account->id() : FALSE;
|
||||
}
|
||||
else {
|
||||
$authenticated = $this->userAuth->authenticate($credentials['name'], $credentials['pass']);
|
||||
}
|
||||
if ($authenticated) {
|
||||
$this->userFloodControl->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name']));
|
||||
$this->userLoginFinalize($account);
|
||||
|
||||
// Send basic metadata about the logged in user.
|
||||
$response_data = [];
|
||||
if ($account->get('uid')->access('view', $account)) {
|
||||
$response_data['current_user']['uid'] = $account->id();
|
||||
}
|
||||
if ($account->get('roles')->access('view', $account)) {
|
||||
$response_data['current_user']['roles'] = $account->getRoles();
|
||||
}
|
||||
if ($account->get('name')->access('view', $account)) {
|
||||
$response_data['current_user']['name'] = $account->getAccountName();
|
||||
}
|
||||
$response_data['csrf_token'] = $this->csrfToken->get('rest');
|
||||
|
||||
$logout_route = $this->routeProvider->getRouteByName('user.logout.http');
|
||||
// Trim '/' off path to match \Drupal\Core\Access\CsrfAccessCheck.
|
||||
$logout_path = ltrim($logout_route->getPath(), '/');
|
||||
$response_data['logout_token'] = $this->csrfToken->get($logout_path);
|
||||
|
||||
$encoded_response_data = $this->serializer->encode($response_data, $format);
|
||||
return new Response($encoded_response_data);
|
||||
}
|
||||
}
|
||||
|
||||
$flood_config = $this->config('user.flood');
|
||||
if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) {
|
||||
$this->userFloodControl->register('user.http_login', $flood_config->get('user_window'), $identifier);
|
||||
}
|
||||
// Always register an IP-based failed login event.
|
||||
$this->userFloodControl->register('user.failed_login_ip', $flood_config->get('ip_window'));
|
||||
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.
|
||||
$identifier = '';
|
||||
if (isset($credentials['name'])) {
|
||||
$identifier = $credentials['name'];
|
||||
$users = $this->userStorage->loadByProperties(['name' => trim($identifier)]);
|
||||
}
|
||||
elseif (isset($credentials['mail'])) {
|
||||
$identifier = $credentials['mail'];
|
||||
$users = $this->userStorage->loadByProperties(['mail' => trim($identifier)]);
|
||||
}
|
||||
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = reset($users);
|
||||
if ($account && $account->id()) {
|
||||
if ($account->isBlocked()) {
|
||||
$this->logger->error('Unable to send password reset email for blocked or not yet activated user %identifier.', [
|
||||
'%identifier' => $identifier,
|
||||
]);
|
||||
return new Response();
|
||||
}
|
||||
|
||||
// Send the password reset email.
|
||||
$mail = _user_mail_notify('password_reset', $account);
|
||||
if (empty($mail)) {
|
||||
throw new BadRequestHttpException('Unable to send email. Contact the site administrator if the problem persists.');
|
||||
}
|
||||
else {
|
||||
$this->logger->info('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.
|
||||
$this->logger->error('Unable to send password reset email for unrecognized username or email address %identifier.', [
|
||||
'%identifier' => $identifier,
|
||||
]);
|
||||
return new Response();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the user is blocked.
|
||||
*
|
||||
* @param string $name
|
||||
* The username.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the user is blocked, otherwise FALSE.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. There
|
||||
* is no replacement.
|
||||
* @see https://www.drupal.org/node/3425340
|
||||
*/
|
||||
protected function userIsBlocked($name) {
|
||||
@trigger_error(__METHOD__ . ' is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3425340', E_USER_DEPRECATED);
|
||||
return user_is_blocked($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the user login.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user.
|
||||
*/
|
||||
protected function userLoginFinalize(UserInterface $user) {
|
||||
user_login_finalize($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out a user.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response object.
|
||||
*/
|
||||
public function logout() {
|
||||
$this->userLogout();
|
||||
return new Response(NULL, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out.
|
||||
*/
|
||||
protected function userLogout() {
|
||||
user_logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user is logged in or not.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The response.
|
||||
*/
|
||||
public function loginStatus() {
|
||||
if ($this->currentUser()->isAuthenticated()) {
|
||||
$response = new Response(self::LOGGED_IN);
|
||||
}
|
||||
else {
|
||||
$response = new Response(self::LOGGED_OUT);
|
||||
}
|
||||
$response->headers->set('Content-Type', 'text/plain');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the format of the current request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
*
|
||||
* @return string
|
||||
* The format of the request.
|
||||
*/
|
||||
protected function getRequestFormat(Request $request) {
|
||||
$format = $request->getRequestFormat();
|
||||
if (!in_array($format, $this->serializerFormats)) {
|
||||
throw new BadRequestHttpException("Unrecognized format: $format.");
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces flood control for the current login request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
* @param string $username
|
||||
* The user name sent for login credentials.
|
||||
*/
|
||||
protected function floodControl(Request $request, $username) {
|
||||
$flood_config = $this->config('user.flood');
|
||||
if (!$this->userFloodControl->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
|
||||
throw new AccessDeniedHttpException('Access is blocked because of IP based flood prevention.', NULL, Response::HTTP_TOO_MANY_REQUESTS);
|
||||
}
|
||||
|
||||
if ($identifier = $this->getLoginFloodIdentifier($request, $username)) {
|
||||
// Don't allow login if the limit for this user has been reached.
|
||||
// Default is to allow 5 failed attempts every 6 hours.
|
||||
if (!$this->userFloodControl->isAllowed('user.http_login', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
|
||||
if ($flood_config->get('uid_only')) {
|
||||
$error_message = sprintf('There have been more than %s failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', $flood_config->get('user_limit'));
|
||||
}
|
||||
else {
|
||||
$error_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
|
||||
}
|
||||
throw new AccessDeniedHttpException($error_message, NULL, Response::HTTP_TOO_MANY_REQUESTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the login identifier for user login flood control.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
* @param string $username
|
||||
* The username supplied in login credentials.
|
||||
*
|
||||
* @return string
|
||||
* The login identifier or if the user does not exist an empty string.
|
||||
*/
|
||||
protected function getLoginFloodIdentifier(Request $request, $username) {
|
||||
$flood_config = $this->config('user.flood');
|
||||
$accounts = $this->userStorage->loadByProperties(['name' => $username, 'status' => 1]);
|
||||
if ($account = reset($accounts)) {
|
||||
if ($flood_config->get('uid_only')) {
|
||||
// Register flood events based on the uid only, so they apply for any
|
||||
// IP address. This is the most secure option.
|
||||
$identifier = $account->id();
|
||||
}
|
||||
else {
|
||||
// The default identifier is a combination of uid and IP address. This
|
||||
// is less secure but more resistant to denial-of-service attacks that
|
||||
// could lock out all users with public user names.
|
||||
$identifier = $account->id() . '-' . $request->getClientIp();
|
||||
}
|
||||
return $identifier;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
442
web/core/modules/user/src/Controller/UserController.php
Normal file
442
web/core/modules/user/src/Controller/UserController.php
Normal file
@ -0,0 +1,442 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Controller;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Flood\FloodInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\user\Form\UserPasswordResetForm;
|
||||
use Drupal\user\UserDataInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* Controller routines for user routes.
|
||||
*/
|
||||
class UserController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The user data service.
|
||||
*
|
||||
* @var \Drupal\user\UserDataInterface
|
||||
*/
|
||||
protected $userData;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The flood service.
|
||||
*
|
||||
* @var \Drupal\Core\Flood\FloodInterface
|
||||
*/
|
||||
protected $flood;
|
||||
|
||||
/**
|
||||
* Constructs a UserController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\user\UserDataInterface $user_data
|
||||
* The user data service.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\Flood\FloodInterface $flood
|
||||
* The flood service.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface|null $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(
|
||||
DateFormatterInterface $date_formatter,
|
||||
UserStorageInterface $user_storage,
|
||||
UserDataInterface $user_data,
|
||||
LoggerInterface $logger,
|
||||
FloodInterface $flood,
|
||||
protected TimeInterface $time,
|
||||
) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->userStorage = $user_storage;
|
||||
$this->userData = $user_data;
|
||||
$this->logger = $logger;
|
||||
$this->flood = $flood;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('date.formatter'),
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('user.data'),
|
||||
$container->get('logger.factory')->get('user'),
|
||||
$container->get('flood'),
|
||||
$container->get('datetime.time'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the user password reset form.
|
||||
*
|
||||
* In order to never disclose a reset link via a referrer header this
|
||||
* controller must always return a redirect response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
* @param int $uid
|
||||
* User ID of the user requesting reset.
|
||||
* @param int $timestamp
|
||||
* The current timestamp.
|
||||
* @param string $hash
|
||||
* Login link hash.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* The redirect response.
|
||||
*/
|
||||
public function resetPass(Request $request, $uid, $timestamp, $hash) {
|
||||
$account = $this->currentUser();
|
||||
// When processing the one-time login link, we have to make sure that a user
|
||||
// isn't already logged in.
|
||||
if ($account->isAuthenticated()) {
|
||||
// The current user is already logged in.
|
||||
if ($account->id() == $uid) {
|
||||
user_logout();
|
||||
// We need to begin the redirect process again because logging out will
|
||||
// destroy the session.
|
||||
return $this->redirect(
|
||||
'user.reset',
|
||||
[
|
||||
'uid' => $uid,
|
||||
'timestamp' => $timestamp,
|
||||
'hash' => $hash,
|
||||
]
|
||||
);
|
||||
}
|
||||
// A different user is already logged in on the computer.
|
||||
else {
|
||||
/** @var \Drupal\user\UserInterface $reset_link_user */
|
||||
$reset_link_user = $this->userStorage->load($uid);
|
||||
if ($reset_link_user && $this->validatePathParameters($reset_link_user, $timestamp, $hash)) {
|
||||
$this->messenger()
|
||||
->addWarning($this->t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. <a href=":logout">Log out</a> and try using the link again.',
|
||||
[
|
||||
'%other_user' => $account->getAccountName(),
|
||||
'%resetting_user' => $reset_link_user->getAccountName(),
|
||||
':logout' => Url::fromRoute('user.logout')->toString(),
|
||||
]));
|
||||
}
|
||||
else {
|
||||
// Invalid one-time link specifies an unknown user.
|
||||
$this->messenger()->addError($this->t('The one-time login link you clicked is invalid.'));
|
||||
}
|
||||
return $this->redirect('<front>');
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \Drupal\user\UserInterface $reset_link_user */
|
||||
$reset_link_user = $this->userStorage->load($uid);
|
||||
if ($redirect = $this->determineErrorRedirect($reset_link_user, $timestamp, $hash)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
$session->set('pass_reset_hash', $hash);
|
||||
$session->set('pass_reset_timeout', $timestamp);
|
||||
return $this->redirect(
|
||||
'user.reset.form',
|
||||
['uid' => $uid]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user password reset form.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
* @param int $uid
|
||||
* User ID of the user requesting reset.
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* The form structure or a redirect response.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* If the pass_reset_timeout or pass_reset_hash are not available in the
|
||||
* session. Or if $uid is for a blocked user or invalid user ID.
|
||||
*/
|
||||
public function getResetPassForm(Request $request, $uid) {
|
||||
$session = $request->getSession();
|
||||
$timestamp = $session->get('pass_reset_timeout');
|
||||
$hash = $session->get('pass_reset_hash');
|
||||
// As soon as the session variables are used they are removed to prevent the
|
||||
// hash and timestamp from being leaked unexpectedly. This could occur if
|
||||
// the user does not click on the log in button on the form.
|
||||
$session->remove('pass_reset_timeout');
|
||||
$session->remove('pass_reset_hash');
|
||||
if (!$hash || !$timestamp) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->userStorage->load($uid);
|
||||
if ($user === NULL || !$user->isActive()) {
|
||||
// Blocked or invalid user ID, so deny access. The parameters will be in
|
||||
// the watchdog's URL for the administrator to check.
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
// Time out, in seconds, until login URL expires.
|
||||
$timeout = $this->config('user.settings')->get('password_reset_timeout');
|
||||
|
||||
$expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL;
|
||||
return $this->formBuilder()->getForm(UserPasswordResetForm::class, $user, $expiration_date, $timestamp, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates user, hash, and timestamp; logs the user in if correct.
|
||||
*
|
||||
* @param int $uid
|
||||
* User ID of the user requesting reset.
|
||||
* @param int $timestamp
|
||||
* The current timestamp.
|
||||
* @param string $hash
|
||||
* Login link hash.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* Returns a redirect to the user edit form if the information is correct.
|
||||
* If the information is incorrect redirects to 'user.pass' route with a
|
||||
* message for the user.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* If $uid is for a blocked user or invalid user ID.
|
||||
*/
|
||||
public function resetPassLogin($uid, $timestamp, $hash, Request $request) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->userStorage->load($uid);
|
||||
if ($redirect = $this->determineErrorRedirect($user, $timestamp, $hash)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$flood_config = $this->config('user.flood');
|
||||
if ($flood_config->get('uid_only')) {
|
||||
$identifier = $user->id();
|
||||
}
|
||||
else {
|
||||
$identifier = $user->id() . '-' . $request->getClientIP();
|
||||
}
|
||||
|
||||
$this->flood->clear('user.failed_login_user', $identifier);
|
||||
$this->flood->clear('user.http_login', $identifier);
|
||||
|
||||
user_login_finalize($user);
|
||||
$this->logger->info('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
|
||||
$this->messenger()->addStatus($this->t('You have used a one-time login link. You can set your new password now.'));
|
||||
// Let the user's password be changed without the current password
|
||||
// check.
|
||||
$token = Crypt::randomBytesBase64(55);
|
||||
$request->getSession()->set('pass_reset_' . $user->id(), $token);
|
||||
// Clear any flood events for this user.
|
||||
$this->flood->clear('user.password_request_user', $uid);
|
||||
return $this->redirect(
|
||||
'entity.user.edit_form',
|
||||
['user' => $user->id()],
|
||||
[
|
||||
'query' => ['pass-reset-token' => $token],
|
||||
'absolute' => TRUE,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates user, hash, and timestamp.
|
||||
*
|
||||
* This method allows the 'user.reset' and 'user.reset.login' routes to use
|
||||
* the same logic to check the user, timestamp and hash and redirect to the
|
||||
* same location with the same messages.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface|null $user
|
||||
* User requesting reset. NULL if the user does not exist.
|
||||
* @param int $timestamp
|
||||
* The current timestamp.
|
||||
* @param string $hash
|
||||
* Login link hash.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
|
||||
* Returns a redirect if the information is incorrect. It redirects to
|
||||
* 'user.pass' route with a message for the user.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* If $uid is for a blocked user or invalid user ID.
|
||||
*/
|
||||
protected function determineErrorRedirect(?UserInterface $user, int $timestamp, string $hash): ?RedirectResponse {
|
||||
// The current user is not logged in, so check the parameters.
|
||||
$current = $this->time->getRequestTime();
|
||||
// Verify that the user exists and is active.
|
||||
if ($user === NULL || !$user->isActive()) {
|
||||
// Blocked or invalid user ID, so deny access. The parameters will be in
|
||||
// the watchdog's URL for the administrator to check.
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
// Time out, in seconds, until login URL expires.
|
||||
$timeout = $this->config('user.settings')->get('password_reset_timeout');
|
||||
// No time out for first time login.
|
||||
if ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
|
||||
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Request a new one using the form below.'));
|
||||
return $this->redirect('user.pass');
|
||||
}
|
||||
elseif ($user->isAuthenticated() && $this->validatePathParameters($user, $timestamp, $hash, $timeout)) {
|
||||
// The information provided is valid.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Request a new one using the form below.'));
|
||||
return $this->redirect('user.pass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates hash and timestamp.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* User requesting reset.
|
||||
* @param int $timestamp
|
||||
* The timestamp.
|
||||
* @param string $hash
|
||||
* Login link hash.
|
||||
* @param int $timeout
|
||||
* Link expiration timeout.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the provided data are valid.
|
||||
*/
|
||||
protected function validatePathParameters(UserInterface $user, int $timestamp, string $hash, int $timeout = 0): bool {
|
||||
$current = \Drupal::time()->getRequestTime();
|
||||
$timeout_valid = ((!empty($timeout) && $current - $timestamp < $timeout) || empty($timeout));
|
||||
return ($timestamp >= $user->getLastLoginTime()) && $timestamp <= $current && $timeout_valid && hash_equals($hash, user_pass_rehash($user, $timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects users to their profile page.
|
||||
*
|
||||
* This controller assumes that it is only invoked for authenticated users.
|
||||
* This is enforced for the 'user.page' route with the '_user_is_logged_in'
|
||||
* requirement.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* Returns a redirect to the profile of the currently logged in user.
|
||||
*/
|
||||
public function userPage() {
|
||||
return $this->redirect('entity.user.canonical', ['user' => $this->currentUser()->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects users to their profile edit page.
|
||||
*
|
||||
* This controller assumes that it is only invoked for authenticated users.
|
||||
* This is typically enforced with the '_user_is_logged_in' requirement.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* Returns a redirect to the profile edit form of the currently logged in
|
||||
* user.
|
||||
*/
|
||||
public function userEditPage() {
|
||||
return $this->redirect('entity.user.edit_form', ['user' => $this->currentUser()->id()], [], 302);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route title callback.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user account.
|
||||
*
|
||||
* @return string|array
|
||||
* The user account name as a render array or an empty string if $user is
|
||||
* NULL.
|
||||
*/
|
||||
public function userTitle(?UserInterface $user = NULL) {
|
||||
return $user ? ['#markup' => $user->getDisplayName(), '#allowed_tags' => Xss::getHtmlTagList()] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current user out.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A redirection to home page.
|
||||
*/
|
||||
public function logout() {
|
||||
if ($this->currentUser()->isAuthenticated()) {
|
||||
user_logout();
|
||||
}
|
||||
return $this->redirect('<front>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms cancelling a user account via an email link.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user account.
|
||||
* @param int $timestamp
|
||||
* The timestamp.
|
||||
* @param string $hashed_pass
|
||||
* The hashed password.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A redirect response.
|
||||
*/
|
||||
public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass = '') {
|
||||
// Time out in seconds until cancel URL expires; 24 hours = 86400 seconds.
|
||||
$timeout = 86400;
|
||||
|
||||
// Basic validation of arguments.
|
||||
$account_data = $this->userData->get('user', $user->id());
|
||||
if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
|
||||
// Validate expiration and hashed password/login.
|
||||
if ($user->id() && $this->validatePathParameters($user, $timestamp, $hashed_pass, $timeout)) {
|
||||
$edit = [
|
||||
'user_cancel_notify' => $account_data['cancel_notify'] ?? $this->config('user.settings')->get('notify.status_canceled'),
|
||||
];
|
||||
user_cancel($edit, $user->id(), $account_data['cancel_method']);
|
||||
// Since user_cancel() is not invoked via Form API, batch processing
|
||||
// needs to be invoked manually and should redirect to the front page
|
||||
// after completion.
|
||||
return batch_process('<front>');
|
||||
}
|
||||
else {
|
||||
$this->messenger()->addError($this->t('You have tried to use an account cancellation link that has expired. Request a new one using the form below.'));
|
||||
return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]);
|
||||
}
|
||||
}
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides routes for the entity permissions form.
|
||||
*
|
||||
* Use this class as a route provider for an entity type such as Vocabulary. It
|
||||
* will provide routes for the entity permissions form.
|
||||
*/
|
||||
class EntityPermissionsRouteProvider implements EntityRouteProviderInterface, EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityPermissionsRouteProvider.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$collection = new RouteCollection();
|
||||
|
||||
$entity_type_id = $entity_type->id();
|
||||
|
||||
if ($entity_permissions_route = $this->getEntityPermissionsRoute($entity_type)) {
|
||||
$collection->add("entity.$entity_type_id.entity_permissions_form", $entity_permissions_route);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity permissions route.
|
||||
*
|
||||
* Built only for entity types that are bundles of other entity types and
|
||||
* define the 'entity-permissions-form' link template.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getEntityPermissionsRoute(EntityTypeInterface $entity_type): ?Route {
|
||||
if (!$entity_type->hasLinkTemplate('entity-permissions-form')) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!$bundle_of_id = $entity_type->getBundleOf()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$entity_type_id = $entity_type->id();
|
||||
$route = new Route(
|
||||
$entity_type->getLinkTemplate('entity-permissions-form'),
|
||||
[
|
||||
'_title' => 'Manage permissions',
|
||||
'_form' => 'Drupal\user\Form\EntityPermissionsForm',
|
||||
'entity_type_id' => $bundle_of_id,
|
||||
'bundle_entity_type' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_permission' => 'administer permissions',
|
||||
],
|
||||
[
|
||||
// Indicate that Drupal\Core\Entity\Enhancer\EntityBundleRouteEnhancer
|
||||
// should set the bundle parameter.
|
||||
'_field_ui' => TRUE,
|
||||
'parameters' => [
|
||||
$entity_type_id => [
|
||||
'type' => "entity:$entity_type_id",
|
||||
'with_config_overrides' => TRUE,
|
||||
],
|
||||
],
|
||||
'_admin_route' => TRUE,
|
||||
]
|
||||
);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides routes for the entity permissions form with a custom access check.
|
||||
*
|
||||
* Use this class or EntityPermissionsRouteProvider as a route provider for an
|
||||
* entity type such as BlockContentType. Either one will provide routes for the
|
||||
* entity permissions form. This class provides a custom access check: it denies
|
||||
* access if there are no entity-specific permissions. If you know that each
|
||||
* entity has permissions, or if the check is too expensive, then use
|
||||
* EntityPermissionsRouteProvider instead of this class.
|
||||
*
|
||||
* @deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use
|
||||
* EntityPermissionsRouteProvider instead.
|
||||
* @see https://www.drupal.org/node/3384745
|
||||
*/
|
||||
class EntityPermissionsRouteProviderWithCheck extends EntityPermissionsRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityPermissionsRoute(EntityTypeInterface $entity_type): ?Route {
|
||||
@trigger_error(__CLASS__ . ' is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use EntityPermissionsRouteProvider instead. See https://www.drupal.org/node/3384745', E_USER_DEPRECATED);
|
||||
$route = parent::getEntityPermissionsRoute($entity_type);
|
||||
if ($route) {
|
||||
$route->setRequirement('_custom_access', '\Drupal\user\Form\EntityPermissionsForm::access');
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
292
web/core/modules/user/src/Entity/Role.php
Normal file
292
web/core/modules/user/src/Entity/Role.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Core\Config\Action\Attribute\ActionMethod;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\Attribute\ConfigEntityType;
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\user\RoleAccessControlHandler;
|
||||
use Drupal\user\RoleForm;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\RoleListBuilder;
|
||||
use Drupal\user\RoleStorage;
|
||||
|
||||
/**
|
||||
* Defines the user role entity class.
|
||||
*/
|
||||
#[ConfigEntityType(
|
||||
id: 'user_role',
|
||||
label: new TranslatableMarkup('Role'),
|
||||
label_collection: new TranslatableMarkup('Roles'),
|
||||
label_singular: new TranslatableMarkup('role'),
|
||||
label_plural: new TranslatableMarkup('roles'),
|
||||
config_prefix: 'role',
|
||||
static_cache: TRUE,
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'weight' => 'weight',
|
||||
'label' => 'label',
|
||||
],
|
||||
handlers: [
|
||||
'storage' => RoleStorage::class,
|
||||
'access' => RoleAccessControlHandler::class,
|
||||
'list_builder' => RoleListBuilder::class,
|
||||
'form' => [
|
||||
'default' => RoleForm::class,
|
||||
'delete' => EntityDeleteForm::class,
|
||||
],
|
||||
],
|
||||
links: [
|
||||
'delete-form' => '/admin/people/roles/manage/{user_role}/delete',
|
||||
'edit-form' => '/admin/people/roles/manage/{user_role}',
|
||||
'edit-permissions-form' => '/admin/people/permissions/{user_role}',
|
||||
'collection' => '/admin/people/roles',
|
||||
],
|
||||
admin_permission: 'administer permissions',
|
||||
label_count: [
|
||||
'singular' => '@count role',
|
||||
'plural' => '@count roles',
|
||||
],
|
||||
config_export: [
|
||||
'id',
|
||||
'label',
|
||||
'weight',
|
||||
'is_admin',
|
||||
'permissions',
|
||||
],
|
||||
)]
|
||||
class Role extends ConfigEntityBase implements RoleInterface {
|
||||
|
||||
/**
|
||||
* The machine name of this role.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The human-readable label of this role.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The weight of this role in administrative listings.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* The permissions belonging to this role.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [];
|
||||
|
||||
/**
|
||||
* An indicator whether the role has all permissions.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_admin = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPermissions() {
|
||||
if ($this->isAdmin()) {
|
||||
return [];
|
||||
}
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->get('weight');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setWeight($weight) {
|
||||
$this->set('weight', $weight);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasPermission($permission) {
|
||||
if ($this->isAdmin()) {
|
||||
return TRUE;
|
||||
}
|
||||
return in_array($permission, $this->permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ActionMethod(adminLabel: new TranslatableMarkup('Add permission to role'))]
|
||||
public function grantPermission($permission) {
|
||||
if ($this->isAdmin()) {
|
||||
return $this;
|
||||
}
|
||||
if (!$this->hasPermission($permission)) {
|
||||
$this->permissions[] = $permission;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function revokePermission($permission) {
|
||||
if ($this->isAdmin()) {
|
||||
return $this;
|
||||
}
|
||||
$this->permissions = array_diff($this->permissions, [$permission]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAdmin() {
|
||||
return (bool) $this->is_admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setIsAdmin($is_admin) {
|
||||
$this->is_admin = $is_admin;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
|
||||
parent::postLoad($storage, $entities);
|
||||
// Sort the queried roles by their weight.
|
||||
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
|
||||
uasort($entities, [static::class, 'sort']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
if (!isset($this->weight)) {
|
||||
// Set a role weight to make this new role last.
|
||||
$this->weight = array_reduce($storage->loadMultiple(), function ($max, $role) {
|
||||
return $max > $role->weight ? $max : $role->weight + 1;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (!$this->isSyncing() && $this->hasTrustedData()) {
|
||||
// Permissions are always ordered alphabetically to avoid conflicts in the
|
||||
// exported configuration. If the save is not trusted then the
|
||||
// configuration will be sorted by StorableConfigBase.
|
||||
sort($this->permissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
parent::calculateDependencies();
|
||||
// Load all permission definitions.
|
||||
$permission_definitions = \Drupal::service('user.permissions')->getPermissions();
|
||||
$valid_permissions = array_intersect($this->permissions, array_keys($permission_definitions));
|
||||
$invalid_permissions = array_diff($this->permissions, $valid_permissions);
|
||||
if (!empty($invalid_permissions)) {
|
||||
\Drupal::logger('user')->error('Non-existent permission(s) assigned to role "@label" (@id) were removed. Invalid permission(s): @permissions.', [
|
||||
'@label' => $this->label(),
|
||||
'@id' => $this->id(),
|
||||
'@permissions' => implode(', ', $invalid_permissions),
|
||||
]);
|
||||
$this->permissions = $valid_permissions;
|
||||
}
|
||||
foreach ($valid_permissions as $permission) {
|
||||
// Depend on the module that is providing this permission.
|
||||
$this->addDependency('module', $permission_definitions[$permission]['provider']);
|
||||
// Depend on any other dependencies defined by permissions granted to
|
||||
// this role.
|
||||
if (!empty($permission_definitions[$permission]['dependencies'])) {
|
||||
$this->addDependencies($permission_definitions[$permission]['dependencies']);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
// Load all permission definitions.
|
||||
$permission_definitions = \Drupal::service('user.permissions')->getPermissions();
|
||||
|
||||
// Convert config and content entity dependencies to a list of names to make
|
||||
// it easier to check.
|
||||
foreach (['content', 'config'] as $type) {
|
||||
$dependencies[$type] = array_keys($dependencies[$type]);
|
||||
}
|
||||
|
||||
// Remove any permissions from the role that are dependent on anything being
|
||||
// deleted or uninstalled.
|
||||
foreach ($this->permissions as $key => $permission) {
|
||||
if (!isset($permission_definitions[$permission])) {
|
||||
// If the permission is not defined then there's nothing we can do.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($permission_definitions[$permission]['provider'], $dependencies['module'], TRUE)) {
|
||||
unset($this->permissions[$key]);
|
||||
$changed = TRUE;
|
||||
// Process the next permission.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($permission_definitions[$permission]['dependencies'])) {
|
||||
foreach ($permission_definitions[$permission]['dependencies'] as $type => $list) {
|
||||
if (array_intersect($list, $dependencies[$type])) {
|
||||
unset($this->permissions[$key]);
|
||||
$changed = TRUE;
|
||||
// Process the next permission.
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all valid permissions.
|
||||
*
|
||||
* @return string[]
|
||||
* All possible valid permissions.
|
||||
*
|
||||
* @see \Drupal\user\PermissionHandler::getPermissions()
|
||||
*
|
||||
* @internal
|
||||
* @todo Revisit in https://www.drupal.org/node/3446364
|
||||
*/
|
||||
public static function getAllValidPermissions(): array {
|
||||
return array_keys(\Drupal::service('user.permissions')->getPermissions());
|
||||
}
|
||||
|
||||
}
|
||||
609
web/core/modules/user/src/Entity/User.php
Normal file
609
web/core/modules/user/src/Entity/User.php
Normal file
@ -0,0 +1,609 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Flood\PrefixFloodInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\user\Form\UserCancelForm;
|
||||
use Drupal\user\ProfileForm;
|
||||
use Drupal\user\ProfileTranslationHandler;
|
||||
use Drupal\user\RegisterForm;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\StatusItem;
|
||||
use Drupal\user\TimeZoneItem;
|
||||
use Drupal\user\UserAccessControlHandler;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserListBuilder;
|
||||
use Drupal\user\UserStorage;
|
||||
use Drupal\user\UserStorageSchema;
|
||||
use Drupal\user\UserViewsData;
|
||||
|
||||
/**
|
||||
* Defines the user entity class.
|
||||
*
|
||||
* The base table name here is plural, despite Drupal table naming standards,
|
||||
* because "user" is a reserved word in many databases.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'user',
|
||||
label: new TranslatableMarkup('User'),
|
||||
label_collection: new TranslatableMarkup('Users'),
|
||||
label_singular: new TranslatableMarkup('user'),
|
||||
label_plural: new TranslatableMarkup('users'),
|
||||
entity_keys: [
|
||||
'id' => 'uid',
|
||||
'langcode' => 'langcode',
|
||||
'uuid' => 'uuid',
|
||||
],
|
||||
handlers: [
|
||||
'storage' => UserStorage::class,
|
||||
'storage_schema' => UserStorageSchema::class,
|
||||
'access' => UserAccessControlHandler::class,
|
||||
'list_builder' => UserListBuilder::class,
|
||||
'views_data' => UserViewsData::class,
|
||||
'route_provider' => [
|
||||
'html' => UserRouteProvider::class,
|
||||
],
|
||||
'form' => [
|
||||
'default' => ProfileForm::class,
|
||||
'cancel' => UserCancelForm::class,
|
||||
'register' => RegisterForm::class,
|
||||
],
|
||||
'translation' => ProfileTranslationHandler::class,
|
||||
],
|
||||
links: [
|
||||
'canonical' => '/user/{user}',
|
||||
'edit-form' => '/user/{user}/edit',
|
||||
'cancel-form' => '/user/{user}/cancel',
|
||||
'collection' => '/admin/people',
|
||||
],
|
||||
admin_permission: 'administer users',
|
||||
base_table: 'users',
|
||||
data_table: 'users_field_data',
|
||||
translatable: TRUE,
|
||||
label_count: [
|
||||
'singular' => '@count user',
|
||||
'plural' => '@count users',
|
||||
],
|
||||
field_ui_base_route: 'entity.user.admin_form',
|
||||
common_reference_target: TRUE,
|
||||
)]
|
||||
class User extends ContentEntityBase implements UserInterface {
|
||||
|
||||
use EntityChangedTrait;
|
||||
|
||||
/**
|
||||
* Stores a reference for a reusable anonymous user entity.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected static $anonymousUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNew() {
|
||||
return !empty($this->enforceIsNew) || $this->id() === NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
// Make sure that the authenticated/anonymous roles are not persisted.
|
||||
foreach ($this->get('roles') as $index => $item) {
|
||||
if (in_array($item->target_id, [RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID])) {
|
||||
$this->get('roles')->offsetUnset($index);
|
||||
}
|
||||
}
|
||||
|
||||
// Store account cancellation information.
|
||||
foreach (['user_cancel_method', 'user_cancel_notify'] as $key) {
|
||||
if (isset($this->{$key})) {
|
||||
\Drupal::service('user.data')->set('user', $this->id(), substr($key, 5), $this->{$key});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
if ($update) {
|
||||
$session_manager = \Drupal::service('session_manager');
|
||||
// If the password has been changed, delete all open sessions for the
|
||||
// user and recreate the current one.
|
||||
if ($this->pass->value != $this->getOriginal()->pass->value) {
|
||||
$session_manager->delete($this->id());
|
||||
if ($this->id() == \Drupal::currentUser()->id()) {
|
||||
\Drupal::service('session')->migrate();
|
||||
}
|
||||
|
||||
$flood_config = \Drupal::config('user.flood');
|
||||
$flood_service = \Drupal::flood();
|
||||
$identifier = $this->id();
|
||||
if ($flood_config->get('uid_only')) {
|
||||
// Clear flood events based on the uid only if configured.
|
||||
$flood_service->clear('user.failed_login_user', $identifier);
|
||||
}
|
||||
elseif ($flood_service instanceof PrefixFloodInterface) {
|
||||
$flood_service->clearByPrefix('user.failed_login_user', $identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If the user was blocked, delete the user's sessions to force a logout.
|
||||
if ($this->getOriginal()->status->value != $this->status->value && $this->status->value == 0) {
|
||||
$session_manager->delete($this->id());
|
||||
}
|
||||
|
||||
// Send emails after we have the new user object.
|
||||
if ($this->status->value != $this->getOriginal()->status->value) {
|
||||
// The user's status is changing; conditionally send notification email.
|
||||
$op = $this->status->value == 1 ? 'status_activated' : 'status_blocked';
|
||||
_user_mail_notify($op, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
$uids = array_keys($entities);
|
||||
\Drupal::service('user.data')->delete(NULL, $uids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoles($exclude_locked_roles = FALSE) {
|
||||
$roles = [];
|
||||
|
||||
// Users with an ID always have the authenticated user role.
|
||||
if (!$exclude_locked_roles) {
|
||||
if ($this->isAuthenticated()) {
|
||||
$roles[] = RoleInterface::AUTHENTICATED_ID;
|
||||
}
|
||||
else {
|
||||
$roles[] = RoleInterface::ANONYMOUS_ID;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->get('roles') as $role) {
|
||||
if ($role->target_id) {
|
||||
$roles[] = $role->target_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasRole($rid) {
|
||||
return in_array($rid, $this->getRoles());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addRole($rid) {
|
||||
|
||||
if (in_array($rid, [RoleInterface::AUTHENTICATED_ID, RoleInterface::ANONYMOUS_ID])) {
|
||||
throw new \InvalidArgumentException('Anonymous or authenticated role ID must not be assigned manually.');
|
||||
}
|
||||
|
||||
$roles = $this->getRoles(TRUE);
|
||||
$roles[] = $rid;
|
||||
$this->set('roles', array_unique($roles));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeRole($rid) {
|
||||
$this->set('roles', array_diff($this->getRoles(TRUE), [$rid]));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasPermission(string $permission) {
|
||||
return \Drupal::service('permission_checker')->hasPermission($permission, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPassword() {
|
||||
return $this->get('pass')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPassword(#[\SensitiveParameter] $password) {
|
||||
$this->get('pass')->value = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEmail() {
|
||||
return $this->get('mail')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEmail($mail) {
|
||||
$this->get('mail')->value = $mail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreatedTime() {
|
||||
return $this->get('created')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastAccessedTime() {
|
||||
return $this->get('access')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLastAccessTime($timestamp) {
|
||||
$this->get('access')->value = $timestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastLoginTime() {
|
||||
return $this->get('login')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLastLoginTime($timestamp) {
|
||||
$this->get('login')->value = $timestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isActive() {
|
||||
return $this->get('status')->value == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isBlocked() {
|
||||
return $this->get('status')->value == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function activate() {
|
||||
if ($this->isAnonymous()) {
|
||||
throw new \LogicException('The anonymous user account should remain blocked at all times.');
|
||||
}
|
||||
$this->get('status')->value = 1;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function block() {
|
||||
$this->get('status')->value = 0;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimeZone() {
|
||||
return $this->get('timezone')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPreferredLangcode($fallback_to_default = TRUE) {
|
||||
$language_list = $this->languageManager()->getLanguages();
|
||||
$preferred_langcode = $this->get('preferred_langcode')->value;
|
||||
if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
|
||||
return $language_list[$preferred_langcode]->getId();
|
||||
}
|
||||
else {
|
||||
return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPreferredAdminLangcode($fallback_to_default = TRUE) {
|
||||
$language_list = $this->languageManager()->getLanguages();
|
||||
$preferred_langcode = $this->get('preferred_admin_langcode')->value;
|
||||
if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
|
||||
return $language_list[$preferred_langcode]->getId();
|
||||
}
|
||||
else {
|
||||
return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInitialEmail() {
|
||||
return $this->get('init')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAuthenticated() {
|
||||
return $this->id() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAnonymous() {
|
||||
return $this->id() === 0 || $this->id() === '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccountName() {
|
||||
return $this->get('name')->value ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDisplayName() {
|
||||
$name = $this->getAccountName() ?: \Drupal::config('user.settings')->get('anonymous');
|
||||
\Drupal::moduleHandler()->alter('user_format_name', $name, $this);
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUsername($username) {
|
||||
$this->set('name', $username);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setExistingPassword(#[\SensitiveParameter] $password) {
|
||||
$this->get('pass')->existing = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkExistingPassword(UserInterface $account_unchanged) {
|
||||
$existing = $this->get('pass')->existing;
|
||||
return $existing !== NULL && strlen($existing) > 0 &&
|
||||
\Drupal::service('password')->check(trim($existing), $account_unchanged->getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an anonymous user entity.
|
||||
*
|
||||
* @return \Drupal\user\UserInterface
|
||||
* An anonymous user entity.
|
||||
*/
|
||||
public static function getAnonymousUser() {
|
||||
if (!isset(static::$anonymousUser)) {
|
||||
|
||||
// @todo Use the entity factory once available, see
|
||||
// https://www.drupal.org/node/1867228.
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$entity_type = $entity_type_manager->getDefinition('user');
|
||||
$class = $entity_type->getClass();
|
||||
|
||||
static::$anonymousUser = new $class([
|
||||
'uid' => [LanguageInterface::LANGCODE_DEFAULT => 0],
|
||||
'name' => [LanguageInterface::LANGCODE_DEFAULT => ''],
|
||||
// Explicitly set the langcode to ensure that field definitions do not
|
||||
// need to be fetched to figure out a default.
|
||||
'langcode' => [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED],
|
||||
], $entity_type->id());
|
||||
}
|
||||
return clone static::$anonymousUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['uid']->setLabel(t('User ID'))
|
||||
->setDescription(t('The user ID.'));
|
||||
|
||||
$fields['uuid']->setDescription(t('The user UUID.'));
|
||||
|
||||
$fields['langcode']->setLabel(t('Language code'))
|
||||
->setDescription(t('The user language code.'))
|
||||
->setDisplayOptions('form', ['region' => 'hidden']);
|
||||
|
||||
$fields['preferred_langcode'] = BaseFieldDefinition::create('language')
|
||||
->setLabel(t('Preferred language code'))
|
||||
->setDescription(t("The user's preferred language code for receiving emails and viewing the site."))
|
||||
// @todo Define this via an options provider once
|
||||
// https://www.drupal.org/node/2329937 is completed.
|
||||
->addPropertyConstraints('value', [
|
||||
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
|
||||
]);
|
||||
|
||||
$fields['preferred_admin_langcode'] = BaseFieldDefinition::create('language')
|
||||
->setLabel(t('Preferred admin language code'))
|
||||
->setDescription(t("The user's preferred language code for viewing administration pages."))
|
||||
// @todo A default value of NULL is ignored, so we have to specify
|
||||
// an empty field item structure instead. Fix this in
|
||||
// https://www.drupal.org/node/2318605.
|
||||
->setDefaultValue([0 => ['value' => NULL]])
|
||||
// @todo Define this via an options provider once
|
||||
// https://www.drupal.org/node/2329937 is completed.
|
||||
->addPropertyConstraints('value', [
|
||||
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
|
||||
]);
|
||||
|
||||
// The name should not vary per language. The username is the visual
|
||||
// identifier for a user and needs to be consistent in all languages.
|
||||
$fields['name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Name'))
|
||||
->setDescription(t('The name of this user.'))
|
||||
->setRequired(TRUE)
|
||||
->setConstraints([
|
||||
// No Length constraint here because the UserName constraint also covers
|
||||
// that.
|
||||
'UserName' => [],
|
||||
'UserNameUnique' => [],
|
||||
]);
|
||||
$fields['name']->getItemDefinition()->setClass('\Drupal\user\UserNameItem');
|
||||
|
||||
$fields['pass'] = BaseFieldDefinition::create('password')
|
||||
->setLabel(t('Password'))
|
||||
->setDescription(t('The password of this user (hashed).'))
|
||||
->addConstraint('ProtectedUserField');
|
||||
|
||||
$fields['mail'] = BaseFieldDefinition::create('email')
|
||||
->setLabel(t('Email'))
|
||||
->setDescription(t('The email of this user.'))
|
||||
->setDefaultValue('')
|
||||
->addConstraint('UserMailUnique')
|
||||
->addConstraint('UserMailRequired')
|
||||
->addConstraint('ProtectedUserField');
|
||||
|
||||
$fields['timezone'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Timezone'))
|
||||
->setDescription(t('The timezone of this user.'))
|
||||
->setSetting('max_length', 32)
|
||||
// @todo Define this via an options provider once
|
||||
// https://www.drupal.org/node/2329937 is completed.
|
||||
->addPropertyConstraints('value', [
|
||||
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'],
|
||||
]);
|
||||
$fields['timezone']->getItemDefinition()->setClass(TimeZoneItem::class);
|
||||
|
||||
$fields['status'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('User status'))
|
||||
->setDescription(t('Whether the user is active or blocked.'))
|
||||
->setDefaultValue(FALSE);
|
||||
$fields['status']->getItemDefinition()->setClass(StatusItem::class);
|
||||
|
||||
$fields['created'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Created'))
|
||||
->setDescription(t('The time that the user was created.'));
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the user was last edited.'))
|
||||
->setTranslatable(TRUE);
|
||||
|
||||
$fields['access'] = BaseFieldDefinition::create('timestamp')
|
||||
->setLabel(t('Last access'))
|
||||
->setDescription(t('The time that the user last accessed the site.'))
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['login'] = BaseFieldDefinition::create('timestamp')
|
||||
->setLabel(t('Last login'))
|
||||
->setDescription(t('The time that the user last logged in.'))
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['init'] = BaseFieldDefinition::create('email')
|
||||
->setLabel(t('Initial email'))
|
||||
->setDescription(t('The email address used for initial account creation.'))
|
||||
->setDefaultValue('');
|
||||
|
||||
$fields['roles'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Roles'))
|
||||
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
|
||||
->setDescription(t('The roles the user has.'))
|
||||
->setSetting('target_type', 'user_role');
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the role storage object.
|
||||
*
|
||||
* @return \Drupal\user\RoleStorageInterface
|
||||
* The role storage object.
|
||||
*/
|
||||
protected function getRoleStorage() {
|
||||
return \Drupal::entityTypeManager()->getStorage('user_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines allowed timezones for the field's AllowedValues constraint.
|
||||
*
|
||||
* @return string[]
|
||||
* The allowed values.
|
||||
*/
|
||||
public static function getAllowedTimezones() {
|
||||
return \DateTimeZone::listIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines allowed configurable language codes for AllowedValues constraints.
|
||||
*
|
||||
* @return string[]
|
||||
* The allowed values.
|
||||
*/
|
||||
public static function getAllowedConfigurableLanguageCodes() {
|
||||
return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE));
|
||||
}
|
||||
|
||||
}
|
||||
52
web/core/modules/user/src/Entity/UserRouteProvider.php
Normal file
52
web/core/modules/user/src/Entity/UserRouteProvider.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides routes for the user entity.
|
||||
*/
|
||||
class UserRouteProvider implements EntityRouteProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$route_collection = new RouteCollection();
|
||||
$route = (new Route('/user/{user}'))
|
||||
->setDefaults([
|
||||
'_entity_view' => 'user.full',
|
||||
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
|
||||
])
|
||||
->setRequirement('user', '\d+')
|
||||
->setRequirement('_entity_access', 'user.view');
|
||||
$route_collection->add('entity.user.canonical', $route);
|
||||
|
||||
$route = (new Route('/user/{user}/edit'))
|
||||
->setDefaults([
|
||||
'_entity_form' => 'user.default',
|
||||
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
|
||||
])
|
||||
->setOption('_admin_route', TRUE)
|
||||
->setRequirement('user', '\d+')
|
||||
->setRequirement('_entity_access', 'user.update');
|
||||
$route_collection->add('entity.user.edit_form', $route);
|
||||
|
||||
$route = (new Route('/user/{user}/cancel'))
|
||||
->setDefaults([
|
||||
'_title' => 'Cancel account',
|
||||
'_entity_form' => 'user.cancel',
|
||||
])
|
||||
->setOption('_admin_route', TRUE)
|
||||
->setRequirement('user', '\d+')
|
||||
->setRequirement('_entity_access', 'user.delete');
|
||||
$route_collection->add('entity.user.cancel_form', $route);
|
||||
|
||||
return $route_collection;
|
||||
}
|
||||
|
||||
}
|
||||
54
web/core/modules/user/src/EntityOwnerInterface.php
Normal file
54
web/core/modules/user/src/EntityOwnerInterface.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
/**
|
||||
* Defines a common interface for entities that have an owner.
|
||||
*
|
||||
* An owner is someone who has primary control over an entity, similar to
|
||||
* owners in Unix file system access. This may or may not be the entity's
|
||||
* original author. The owner may also have less permissions than other users,
|
||||
* such as administrators.
|
||||
*
|
||||
* @ingroup entity_type_characteristics
|
||||
*/
|
||||
interface EntityOwnerInterface {
|
||||
|
||||
/**
|
||||
* Returns the entity owner's user entity.
|
||||
*
|
||||
* @return \Drupal\user\UserInterface
|
||||
* The owner user entity.
|
||||
*/
|
||||
public function getOwner();
|
||||
|
||||
/**
|
||||
* Sets the entity owner's user entity.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* The owner user entity.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOwner(UserInterface $account);
|
||||
|
||||
/**
|
||||
* Returns the entity owner's user ID.
|
||||
*
|
||||
* @return int|null
|
||||
* The owner user ID, or NULL in case the user ID field has not been set on
|
||||
* the entity.
|
||||
*/
|
||||
public function getOwnerId();
|
||||
|
||||
/**
|
||||
* Sets the entity owner's user ID.
|
||||
*
|
||||
* @param int $uid
|
||||
* The owner user id.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOwnerId($uid);
|
||||
|
||||
}
|
||||
90
web/core/modules/user/src/EntityOwnerTrait.php
Normal file
90
web/core/modules/user/src/EntityOwnerTrait.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Provides a trait for entities that have an owner.
|
||||
*/
|
||||
trait EntityOwnerTrait {
|
||||
|
||||
/**
|
||||
* Returns an array of base field definitions for entity owners.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type to add the owner field to.
|
||||
*
|
||||
* @return \Drupal\Core\Field\BaseFieldDefinition[]
|
||||
* An array of base field definitions.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\Exception\UnsupportedEntityTypeDefinitionException
|
||||
* Thrown when the entity type does not implement EntityOwnerInterface or
|
||||
* if it does not have an "owner" entity key.
|
||||
*/
|
||||
public static function ownerBaseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
if (!is_subclass_of($entity_type->getClass(), EntityOwnerInterface::class)) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not implement \Drupal\user\EntityOwnerInterface.');
|
||||
}
|
||||
if (!$entity_type->hasKey('owner')) {
|
||||
throw new UnsupportedEntityTypeDefinitionException('The entity type ' . $entity_type->id() . ' does not have an "owner" entity key.');
|
||||
}
|
||||
|
||||
return [
|
||||
$entity_type->getKey('owner') => BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(new TranslatableMarkup('User ID'))
|
||||
->setSetting('target_type', 'user')
|
||||
->setTranslatable($entity_type->isTranslatable())
|
||||
->setDefaultValueCallback(static::class . '::getDefaultEntityOwner'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOwnerId() {
|
||||
return $this->getEntityKey('owner');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOwnerId($uid) {
|
||||
$key = $this->getEntityType()->getKey('owner');
|
||||
$this->set($key, $uid);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOwner() {
|
||||
$key = $this->getEntityType()->getKey('owner');
|
||||
return $this->get($key)->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOwner(UserInterface $account) {
|
||||
$key = $this->getEntityType()->getKey('owner');
|
||||
$this->set($key, $account);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value callback for 'owner' base field.
|
||||
*
|
||||
* @return mixed
|
||||
* A default value for the owner field.
|
||||
*/
|
||||
public static function getDefaultEntityOwner() {
|
||||
return \Drupal::currentUser()->id();
|
||||
}
|
||||
|
||||
}
|
||||
43
web/core/modules/user/src/Event/UserEvents.php
Normal file
43
web/core/modules/user/src/Event/UserEvents.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Event;
|
||||
|
||||
/**
|
||||
* Defines events for the user module.
|
||||
*/
|
||||
final class UserEvents {
|
||||
|
||||
/**
|
||||
* The name of the event fired when a login is blocked by flood control.
|
||||
*
|
||||
* This event allows modules to perform an action whenever flood control has
|
||||
* been triggered by excessive login attempts for a particular user account.
|
||||
* The event listener method receives a \Drupal\user\Event\UserFloodEvent
|
||||
* instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see: \Drupal\user\UserFloodControl::isAllowed
|
||||
* @see: \Drupal\user\EventSubscriber\UserFloodSubscriber
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FLOOD_BLOCKED_USER = 'user.flood_blocked_user';
|
||||
|
||||
/**
|
||||
* The name of the event fired when a login is blocked by flood control.
|
||||
*
|
||||
* This event allows modules to perform an action whenever flood control has
|
||||
* been triggered by excessive login attempts from a particular IP. The event
|
||||
* listener method receives a \Drupal\user\Event\UserFloodEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see: \Drupal\user\UserFloodControl::isAllowed
|
||||
* @see: \Drupal\user\EventSubscriber\UserFloodSubscriber
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FLOOD_BLOCKED_IP = 'user.flood_blocked_ip';
|
||||
|
||||
}
|
||||
165
web/core/modules/user/src/Event/UserFloodEvent.php
Normal file
165
web/core/modules/user/src/Event/UserFloodEvent.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Event;
|
||||
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Provides a user flood event for event listeners.
|
||||
*/
|
||||
class UserFloodEvent extends Event {
|
||||
|
||||
/**
|
||||
* Flood event name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Flood event threshold.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $threshold;
|
||||
|
||||
/**
|
||||
* Flood event window.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $window;
|
||||
|
||||
/**
|
||||
* Flood event identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* Flood event uid.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $uid = NULL;
|
||||
|
||||
/**
|
||||
* Flood event IP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ip = NULL;
|
||||
|
||||
/**
|
||||
* Constructs a user flood event object.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the flood event.
|
||||
* @param int $threshold
|
||||
* The threshold for the flood event.
|
||||
* @param int $window
|
||||
* The window for the flood event.
|
||||
* @param string $identifier
|
||||
* The identifier of the flood event.
|
||||
*/
|
||||
public function __construct($name, $threshold, $window, $identifier) {
|
||||
$this->name = $name;
|
||||
$this->threshold = $threshold;
|
||||
$this->window = $window;
|
||||
$this->identifier = $identifier;
|
||||
// The identifier could be a uid or an IP, or a composite of both.
|
||||
if (is_numeric($identifier)) {
|
||||
$this->uid = $identifier;
|
||||
return;
|
||||
}
|
||||
if (str_contains($identifier, '-')) {
|
||||
[$uid, $ip] = explode('-', $identifier);
|
||||
$this->uid = $uid;
|
||||
$this->ip = $ip;
|
||||
return;
|
||||
}
|
||||
$this->ip = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the user flood event object.
|
||||
*
|
||||
* @return string
|
||||
* The name of the flood event.
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the threshold for the user flood event object.
|
||||
*
|
||||
* @return int
|
||||
* The threshold for the flood event.
|
||||
*/
|
||||
public function getThreshold() {
|
||||
return $this->threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the window for the user flood event object.
|
||||
*
|
||||
* @return int
|
||||
* The window for the flood event.
|
||||
*/
|
||||
public function getWindow() {
|
||||
return $this->window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifier of the user flood event object.
|
||||
*
|
||||
* @return string
|
||||
* The identifier of the flood event.
|
||||
*/
|
||||
public function getIdentifier() {
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the IP of the user flood event object.
|
||||
*
|
||||
* @return string
|
||||
* The IP of the flood event.
|
||||
*/
|
||||
public function getIp() {
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uid of the user flood event object.
|
||||
*
|
||||
* @return int
|
||||
* The uid of the flood event.
|
||||
*/
|
||||
public function getUid() {
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user flood event associated with an IP?
|
||||
*
|
||||
* @return bool
|
||||
* Whether the event has an IP.
|
||||
*/
|
||||
public function hasIp() {
|
||||
return !empty($this->ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the user flood event associated with a uid?
|
||||
*
|
||||
* @return bool
|
||||
* Whether the event has a uid.
|
||||
*/
|
||||
public function hasUid() {
|
||||
return !empty($this->uid);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\EventSubscriber;
|
||||
|
||||
use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
|
||||
/**
|
||||
* Redirects users when access is denied.
|
||||
*
|
||||
* Anonymous users are taken to the login page when attempting to access the
|
||||
* user profile pages. Authenticated users are redirected from the login form to
|
||||
* their profile page and from the user registration form to their profile edit
|
||||
* form.
|
||||
*/
|
||||
class AccessDeniedSubscriber extends HttpExceptionSubscriberBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a new redirect subscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(AccountInterface $account) {
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getHandledFormats(): array {
|
||||
return ['html'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getPriority(): int {
|
||||
// Use a higher priority than ExceptionLoggingSubscriber, because there's
|
||||
// no need to log the exception if we can redirect.
|
||||
// @see Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber
|
||||
return 75;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects users when access is denied.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function on403(ExceptionEvent $event): void {
|
||||
$route_name = RouteMatch::createFromRequest($event->getRequest())->getRouteName();
|
||||
$redirect_url = NULL;
|
||||
if ($this->account->isAuthenticated()) {
|
||||
switch ($route_name) {
|
||||
case 'user.login':
|
||||
// Redirect an authenticated user to the profile page.
|
||||
$redirect_url = Url::fromRoute('entity.user.canonical', ['user' => $this->account->id()], ['absolute' => TRUE]);
|
||||
break;
|
||||
|
||||
case 'user.register':
|
||||
// Redirect an authenticated user to the profile form.
|
||||
$redirect_url = Url::fromRoute('entity.user.edit_form', ['user' => $this->account->id()], ['absolute' => TRUE]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
elseif ($route_name === 'user.page') {
|
||||
$redirect_url = Url::fromRoute('user.login', [], ['absolute' => TRUE]);
|
||||
}
|
||||
elseif (in_array($route_name, ['user.logout', 'user.logout.confirm'], TRUE)) {
|
||||
$redirect_url = Url::fromRoute('<front>', [], ['absolute' => TRUE]);
|
||||
}
|
||||
|
||||
if ($redirect_url) {
|
||||
$event->setResponse(new RedirectResponse($redirect_url->toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Site\MaintenanceModeEvents;
|
||||
use Drupal\Core\Site\MaintenanceModeInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
|
||||
/**
|
||||
* Maintenance mode subscriber to log out users.
|
||||
*/
|
||||
class MaintenanceModeSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The maintenance mode.
|
||||
*
|
||||
* @var \Drupal\Core\Site\MaintenanceMode
|
||||
*/
|
||||
protected $maintenanceMode;
|
||||
|
||||
/**
|
||||
* The current account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a new MaintenanceModeSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode
|
||||
* The maintenance mode.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(MaintenanceModeInterface $maintenance_mode, AccountInterface $account) {
|
||||
$this->maintenanceMode = $maintenance_mode;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout users if site is in maintenance mode and user is not exempt.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onMaintenanceModeRequest(RequestEvent $event) {
|
||||
// If the site is offline, log out unprivileged users.
|
||||
if ($this->account->isAuthenticated()) {
|
||||
user_logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST][] = [
|
||||
'onMaintenanceModeRequest',
|
||||
-900,
|
||||
];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\EventSubscriber;
|
||||
|
||||
use Drupal\user\Event\UserEvents;
|
||||
use Drupal\user\Event\UserFloodEvent;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Logs details of User Flood Control events.
|
||||
*/
|
||||
class UserFloodSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The default logger service.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a UserFloodSubscriber.
|
||||
*
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(?LoggerInterface $logger = NULL) {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[UserEvents::FLOOD_BLOCKED_USER][] = ['blockedUser'];
|
||||
$events[UserEvents::FLOOD_BLOCKED_IP][] = ['blockedIp'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* An attempt to login has been blocked based on user name.
|
||||
*
|
||||
* @param \Drupal\user\Event\UserFloodEvent $floodEvent
|
||||
* The flood event.
|
||||
*/
|
||||
public function blockedUser(UserFloodEvent $floodEvent) {
|
||||
if (Settings::get('log_user_flood', TRUE)) {
|
||||
$uid = $floodEvent->getUid();
|
||||
if ($floodEvent->hasIp()) {
|
||||
$ip = $floodEvent->getIp();
|
||||
$this->logger->notice('Flood control blocked login attempt for uid %uid from %ip', ['%uid' => $uid, '%ip' => $ip]);
|
||||
return;
|
||||
}
|
||||
$this->logger->notice('Flood control blocked login attempt for uid %uid', ['%uid' => $uid]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An attempt to login has been blocked based on IP.
|
||||
*
|
||||
* @param \Drupal\user\Event\UserFloodEvent $floodEvent
|
||||
* The flood event.
|
||||
*/
|
||||
public function blockedIp(UserFloodEvent $floodEvent) {
|
||||
if (Settings::get('log_user_flood', TRUE)) {
|
||||
$this->logger->notice('Flood control blocked login attempt from %ip', ['%ip' => $floodEvent->getIp()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\EventSubscriber;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Updates the current user's last access time.
|
||||
*/
|
||||
class UserRequestSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The current account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new UserRequestSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(AccountInterface $account, EntityTypeManagerInterface $entity_type_manager, protected TimeInterface $time) {
|
||||
$this->account = $account;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current user's last access time.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onKernelTerminate(TerminateEvent $event) {
|
||||
if ($this->account->isAuthenticated() && $this->time->getRequestTime() - $this->account->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
|
||||
// Do that no more than once per 180 seconds.
|
||||
/** @var \Drupal\user\UserStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage('user');
|
||||
$storage->updateLastAccessTimestamp($this->account, $this->time->getRequestTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
// Should go before other subscribers start to write their caches.
|
||||
$events[KernelEvents::TERMINATE][] = ['onKernelTerminate', 300];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
192
web/core/modules/user/src/Form/EntityPermissionsForm.php
Normal file
192
web/core/modules/user/src/Form/EntityPermissionsForm.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Config\ConfigManagerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\user\PermissionHandlerInterface;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides the permissions administration form for a bundle.
|
||||
*
|
||||
* This class handles bundles that are defined by configuration objects.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class EntityPermissionsForm extends UserPermissionsForm {
|
||||
|
||||
/**
|
||||
* The configuration entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The bundle object.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityPermissionsForm.
|
||||
*
|
||||
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
|
||||
* The permission handler.
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration entity manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList|null $module_extension_list
|
||||
* The module extension list.
|
||||
*/
|
||||
public function __construct(PermissionHandlerInterface $permission_handler, RoleStorageInterface $role_storage, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, EntityTypeManagerInterface $entity_type_manager, ?ModuleExtensionList $module_extension_list = NULL) {
|
||||
if ($module_extension_list === NULL) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() without the $module_extension_list argument is deprecated in drupal:10.3.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3310017', E_USER_DEPRECATED);
|
||||
$module_extension_list = \Drupal::service('extension.list.module');
|
||||
}
|
||||
parent::__construct($permission_handler, $role_storage, $module_handler, $module_extension_list);
|
||||
$this->configManager = $config_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.permissions'),
|
||||
$container->get('entity_type.manager')->getStorage('user_role'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('config.manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('extension.list.module'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function permissionsByProvider(): array {
|
||||
// Get the names of all config entities that depend on $this->bundle.
|
||||
$config_name = $this->bundle->getConfigDependencyName();
|
||||
$config_entities = $this->configManager
|
||||
->findConfigEntityDependencies('config', [$config_name]);
|
||||
$config_names = array_map(
|
||||
fn($dependent_config) => $dependent_config->getConfigDependencyName(),
|
||||
$config_entities,
|
||||
);
|
||||
$config_names[] = $config_name;
|
||||
|
||||
// Find all the permissions that depend on $this->bundle.
|
||||
$permissions = $this->permissionHandler->getPermissions();
|
||||
$permissions_by_provider = [];
|
||||
foreach ($permissions as $permission_name => $permission) {
|
||||
$required_configs = $permission['dependencies']['config'] ?? [];
|
||||
if (array_intersect($required_configs, $config_names)) {
|
||||
$provider = $permission['provider'];
|
||||
$permissions_by_provider[$provider][$permission_name] = $permission;
|
||||
}
|
||||
}
|
||||
|
||||
return $permissions_by_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the user permissions administration form for a bundle.
|
||||
*
|
||||
* @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 string $bundle_entity_type
|
||||
* (optional) The entity type ID.
|
||||
* @param string|\Drupal\Core\Entity\EntityInterface $bundle
|
||||
* (optional) Either the bundle name or the bundle object.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?string $bundle_entity_type = NULL, $bundle = NULL): array {
|
||||
// Set $this->bundle for use by ::permissionsByProvider().
|
||||
if ($bundle instanceof EntityInterface) {
|
||||
$this->bundle = $bundle;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
$this->bundle = $this->entityTypeManager
|
||||
->getStorage($bundle_entity_type)
|
||||
->load($bundle);
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that there are permissions to be managed.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The parametrized route.
|
||||
* @param string|EntityInterface $bundle
|
||||
* (optional) The bundle. Different entity types can have different names
|
||||
* for their bundle key, so if not specified on the route via a {bundle}
|
||||
* parameter, the access checker determines the appropriate key name, and
|
||||
* gets the value from the corresponding request attribute. For example,
|
||||
* for nodes, the bundle key is "node_type", so the value would be
|
||||
* available via the {node_type} parameter rather than a {bundle}
|
||||
* parameter.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*
|
||||
* @deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use
|
||||
* a permissions check in the route definition instead.
|
||||
* @see https://www.drupal.org/node/3384745
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, $bundle = NULL): AccessResultInterface {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use a permissions check on the route definition instead. See https://www.drupal.org/node/3384745', E_USER_DEPRECATED);
|
||||
$permission = $route->getRequirement('_permission');
|
||||
if ($permission && !$this->currentUser()->hasPermission($permission)) {
|
||||
return AccessResult::neutral()->cachePerPermissions();
|
||||
}
|
||||
// Set $this->bundle for use by ::permissionsByProvider().
|
||||
if ($bundle instanceof EntityInterface) {
|
||||
$this->bundle = $bundle;
|
||||
}
|
||||
else {
|
||||
$bundle_entity_type = $route->getDefault('bundle_entity_type');
|
||||
$bundle_name = is_string($bundle) ? $bundle : $route_match->getRawParameter($bundle_entity_type);
|
||||
$this->bundle = $this->entityTypeManager
|
||||
->getStorage($bundle_entity_type)
|
||||
->load($bundle_name);
|
||||
}
|
||||
|
||||
if (empty($this->bundle)) {
|
||||
// A typo in the request path can lead to this case.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
return AccessResult::allowedIf((bool) $this->permissionsByProvider());
|
||||
}
|
||||
|
||||
}
|
||||
109
web/core/modules/user/src/Form/RoleSettingsForm.php
Normal file
109
web/core/modules/user/src/Form/RoleSettingsForm.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configure administrator role settings for this site.
|
||||
*/
|
||||
class RoleSettingsForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The role storage used when changing the admin role.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorageInterface
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\user\Form\RoleSettingsForm object.
|
||||
*
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
*/
|
||||
public function __construct(RoleStorageInterface $role_storage) {
|
||||
$this->roleStorage = $role_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('user_role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'role_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Administrative role option.
|
||||
$form['admin_role'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Administrator role'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
// Do not allow users to set the anonymous or authenticated user roles as
|
||||
// the administrator role.
|
||||
$roles = $this->roleStorage->loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
unset($roles[RoleInterface::AUTHENTICATED_ID]);
|
||||
$roles = array_map(fn(RoleInterface $role) => $role->label(), $roles);
|
||||
$admin_roles = $this->roleStorage->getQuery()
|
||||
->condition('is_admin', TRUE)
|
||||
->execute();
|
||||
$default_value = reset($admin_roles);
|
||||
$form['admin_role']['user_admin_role'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Administrator role'),
|
||||
'#empty_value' => '',
|
||||
'#default_value' => $default_value,
|
||||
'#options' => $roles,
|
||||
'#description' => $this->t('This role will be automatically granted all permissions.'),
|
||||
// Don't allow to select a single admin role in case multiple roles got
|
||||
// marked as admin role already.
|
||||
'#access' => count($admin_roles) <= 1,
|
||||
];
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save configuration'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasValue('user_admin_role')) {
|
||||
$admin_roles = $this->roleStorage->getQuery()
|
||||
->condition('is_admin', TRUE)
|
||||
->execute();
|
||||
foreach ($admin_roles as $rid) {
|
||||
$this->roleStorage->load($rid)->setIsAdmin(FALSE)->save();
|
||||
}
|
||||
$new_admin_role = $form_state->getValue('user_admin_role');
|
||||
if ($new_admin_role) {
|
||||
$this->roleStorage->load($new_admin_role)->setIsAdmin(TRUE)->save();
|
||||
}
|
||||
$this->messenger()->addStatus($this->t('The role settings have been updated.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
167
web/core/modules/user/src/Form/UserCancelForm.php
Normal file
167
web/core/modules/user/src/Form/UserCancelForm.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a confirmation form for cancelling user account.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserCancelForm extends ContentEntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* Available account cancellation methods.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cancelMethods;
|
||||
|
||||
/**
|
||||
* Whether it is allowed to select cancellation method.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $selectCancel;
|
||||
|
||||
/**
|
||||
* The account being cancelled.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
if ($this->entity->id() == $this->currentUser()->id()) {
|
||||
return $this->t('Are you sure you want to cancel your account?');
|
||||
}
|
||||
return $this->t('Are you sure you want to cancel the account %name?', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->entity->toUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
if ($this->selectCancel) {
|
||||
return '';
|
||||
}
|
||||
$default_method = $this->config('user.settings')->get('cancel_method');
|
||||
$own_account = $this->entity->id() == $this->currentUser()->id();
|
||||
// Options supplied via user_cancel_methods() can have a custom
|
||||
// #confirm_description property for the confirmation form description. This
|
||||
// text refers to "Your account" so only user it if cancelling own account.
|
||||
if ($own_account && isset($this->cancelMethods[$default_method]['#confirm_description'])) {
|
||||
return $this->cancelMethods[$default_method]['#confirm_description'];
|
||||
}
|
||||
|
||||
return $this->cancelMethods['#options'][$default_method];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$user = $this->currentUser();
|
||||
$this->cancelMethods = user_cancel_methods();
|
||||
|
||||
// Display account cancellation method selection, if allowed.
|
||||
$own_account = $this->entity->id() == $user->id();
|
||||
$this->selectCancel = $user->hasPermission('administer users') || $user->hasPermission('select account cancellation method');
|
||||
|
||||
$form['user_cancel_method'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $own_account ? $this->t('When cancelling your account') : $this->t('Cancellation method'),
|
||||
'#access' => $this->selectCancel,
|
||||
];
|
||||
$form['user_cancel_method'] += $this->cancelMethods;
|
||||
|
||||
// When managing another user, can skip the account cancellation
|
||||
// confirmation mail (by default).
|
||||
$override_access = !$own_account;
|
||||
$form['user_cancel_confirm'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Require email confirmation'),
|
||||
'#default_value' => !$override_access,
|
||||
'#access' => $override_access,
|
||||
'#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'),
|
||||
];
|
||||
// Also allow to send account canceled notification mail, if enabled.
|
||||
$default_notify = $this->config('user.settings')->get('notify.status_canceled');
|
||||
$form['user_cancel_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is canceled'),
|
||||
'#default_value' => ($override_access ? FALSE : $default_notify),
|
||||
'#access' => $override_access && $default_notify,
|
||||
'#description' => $this->t('When enabled, the user will receive an email notification after the account has been canceled.'),
|
||||
];
|
||||
|
||||
// Always provide entity id in the same form key as in the entity edit form.
|
||||
$form['uid'] = ['#type' => 'value', '#value' => $this->entity->id()];
|
||||
|
||||
// Store the user permissions so that it can be altered in hook_form_alter()
|
||||
// if desired.
|
||||
$form['access'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => !$own_account,
|
||||
];
|
||||
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Cancel account immediately, if the current user has administrative
|
||||
// privileges, no confirmation mail shall be sent, and the user does not
|
||||
// attempt to cancel the own account.
|
||||
if (!$form_state->isValueEmpty('access') && $form_state->isValueEmpty('user_cancel_confirm') && $this->entity->id() != $this->currentUser()->id()) {
|
||||
user_cancel($form_state->getValues(), $this->entity->id(), $form_state->getValue('user_cancel_method'));
|
||||
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
|
||||
}
|
||||
else {
|
||||
// Store cancelling method and whether to notify the user in
|
||||
// $this->entity for
|
||||
// \Drupal\user\Controller\UserController::confirmCancel().
|
||||
$this->entity->user_cancel_method = $form_state->getValue('user_cancel_method');
|
||||
$this->entity->user_cancel_notify = $form_state->getValue('user_cancel_notify');
|
||||
$this->entity->save();
|
||||
_user_mail_notify('cancel_confirm', $this->entity);
|
||||
$this->logger('user')->info('Sent account cancellation request to %name %email.', ['%name' => $this->entity->label(), '%email' => '<' . $this->entity->getEmail() . '>']);
|
||||
|
||||
$cancel_message = $this->t('A confirmation request to cancel your account has been sent to your email address.');
|
||||
if ($this->entity->id() !== $this->currentUser()->id()) {
|
||||
$cancel_message = $this->t("A confirmation request to cancel the account %name has been sent to the user's email address.", ['%name' => $this->entity->label()]);
|
||||
}
|
||||
$this->messenger()->addStatus($cancel_message);
|
||||
|
||||
$form_state->setRedirect(
|
||||
'entity.user.canonical',
|
||||
['user' => $this->entity->id()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
299
web/core/modules/user/src/Form/UserLoginForm.php
Normal file
299
web/core/modules/user/src/Form/UserLoginForm.php
Normal file
@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Render\BareHtmlPageRendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\user\UserAuthenticationInterface;
|
||||
use Drupal\user\UserAuthInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Drupal\user\UserFloodControlInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a user login form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserLoginForm extends FormBase implements WorkspaceSafeFormInterface {
|
||||
|
||||
/**
|
||||
* The user flood control service.
|
||||
*
|
||||
* @var \Drupal\user\UserFloodControl
|
||||
*/
|
||||
protected $userFloodControl;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The user authentication object.
|
||||
*
|
||||
* @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
|
||||
*/
|
||||
protected $userAuth;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The bare HTML renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\BareHtmlPageRendererInterface
|
||||
*/
|
||||
protected $bareHtmlPageRenderer;
|
||||
|
||||
/**
|
||||
* Constructs a new UserLoginForm.
|
||||
*
|
||||
* @param \Drupal\user\UserFloodControlInterface $user_flood_control
|
||||
* The user flood control service.
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface $user_auth
|
||||
* The user authentication object.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(UserFloodControlInterface $user_flood_control, UserStorageInterface $user_storage, UserAuthInterface|UserAuthenticationInterface $user_auth, RendererInterface $renderer, BareHtmlPageRendererInterface $bare_html_renderer) {
|
||||
$this->userFloodControl = $user_flood_control;
|
||||
$this->userStorage = $user_storage;
|
||||
if (!$user_auth instanceof UserAuthenticationInterface) {
|
||||
@trigger_error('The $user_auth parameter not implementing UserAuthenticationInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3411040');
|
||||
}
|
||||
$this->userAuth = $user_auth;
|
||||
$this->renderer = $renderer;
|
||||
$this->bareHtmlPageRenderer = $bare_html_renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.flood_control'),
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('user.auth'),
|
||||
$container->get('renderer'),
|
||||
$container->get('bare_html_page_renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_login_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('system.site');
|
||||
|
||||
// Display login form:
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Username'),
|
||||
'#size' => 60,
|
||||
'#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
|
||||
'#required' => TRUE,
|
||||
'#attributes' => [
|
||||
'autocorrect' => 'none',
|
||||
'autocapitalize' => 'none',
|
||||
'spellcheck' => 'false',
|
||||
'autofocus' => 'autofocus',
|
||||
'autocomplete' => 'username',
|
||||
],
|
||||
];
|
||||
|
||||
$form['pass'] = [
|
||||
'#type' => 'password',
|
||||
'#title' => $this->t('Password'),
|
||||
'#size' => 60,
|
||||
'#required' => TRUE,
|
||||
'#attributes' => [
|
||||
'autocomplete' => 'current-password',
|
||||
],
|
||||
];
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')];
|
||||
|
||||
$form['#validate'][] = '::validateAuthentication';
|
||||
$form['#validate'][] = '::validateFinal';
|
||||
|
||||
$this->renderer->addCacheableDependency($form, $config);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
if (empty($uid = $form_state->get('uid'))) {
|
||||
return;
|
||||
}
|
||||
$account = $this->userStorage->load($uid);
|
||||
|
||||
// A destination was set, probably on an exception controller.
|
||||
if (!$this->getRequest()->request->has('destination')) {
|
||||
$form_state->setRedirect(
|
||||
'entity.user.canonical',
|
||||
['user' => $account->id()]
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->getRequest()->query->set('destination', $this->getRequest()->request->get('destination'));
|
||||
}
|
||||
|
||||
user_login_finalize($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks supplied username/password against local users table.
|
||||
*
|
||||
* If successful, $form_state->get('uid') is set to the matching user ID.
|
||||
*/
|
||||
public function validateAuthentication(array &$form, FormStateInterface $form_state) {
|
||||
$password = trim($form_state->getValue('pass'));
|
||||
$flood_config = $this->config('user.flood');
|
||||
$account = FALSE;
|
||||
if (!$form_state->isValueEmpty('name') && strlen($password) > 0) {
|
||||
// Do not allow any login from the current user's IP if the limit has been
|
||||
// reached. Default is 50 failed attempts allowed in one hour. This is
|
||||
// independent of the per-user limit to catch attempts from one IP to log
|
||||
// in to many different user accounts. We have a reasonably high limit
|
||||
// since there may be only one apparent IP for all users at an
|
||||
// institution.
|
||||
if (!$this->userFloodControl->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
|
||||
$form_state->set('flood_control_triggered', 'ip');
|
||||
return;
|
||||
}
|
||||
if ($this->userAuth instanceof UserAuthenticationInterface) {
|
||||
$account = $this->userAuth->lookupAccount($form_state->getValue('name'));
|
||||
}
|
||||
else {
|
||||
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
|
||||
$account = reset($accounts);
|
||||
}
|
||||
if ($account && $account->isBlocked()) {
|
||||
$form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')]));
|
||||
}
|
||||
elseif ($account && $account->isActive()) {
|
||||
if ($flood_config->get('uid_only')) {
|
||||
// Register flood events based on the uid only, so they apply for any
|
||||
// IP address. This is the most secure option.
|
||||
$identifier = $account->id();
|
||||
}
|
||||
else {
|
||||
// The default identifier is a combination of uid and IP address. This
|
||||
// is less secure but more resistant to denial-of-service attacks that
|
||||
// could lock out all users with public user names.
|
||||
$identifier = $account->id() . '-' . $this->getRequest()->getClientIP();
|
||||
}
|
||||
$form_state->set('flood_control_user_identifier', $identifier);
|
||||
|
||||
// If there are zero flood records for this user, then we don't need to
|
||||
// clear any failed login attempts after a successful login, so check
|
||||
// for this case first before checking the actual flood limit and store
|
||||
// the result in form state.
|
||||
if (!$this->userFloodControl->isAllowed('user.failed_login_user', 1, $flood_config->get('user_window'), $identifier)) {
|
||||
// Now check the actual limit for the user. Default is to allow 5
|
||||
// failed attempts every 6 hours. This means we check the flood table
|
||||
// twice if flood control has already been triggered by a previous
|
||||
// login attempt, but this should be the less common case.
|
||||
if (!$this->userFloodControl->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
|
||||
$form_state->set('flood_control_triggered', 'user');
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form_state->set('flood_control_skip_clear', 'user');
|
||||
}
|
||||
// We are not limited by flood control, so try to authenticate.
|
||||
// Store the user ID in form state as a flag for self::validateFinal().
|
||||
if ($this->userAuth instanceof UserAuthenticationInterface) {
|
||||
$form_state->set('uid', $this->userAuth->authenticateAccount($account, $password) ? $account->id() : FALSE);
|
||||
}
|
||||
// The userAuth object is decorated by an object that that has not
|
||||
// been upgraded to the new UserAuthenticationInterface. Fallback
|
||||
// to the authenticate() method.
|
||||
else {
|
||||
$uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
|
||||
$form_state->set('uid', $uid);
|
||||
}
|
||||
}
|
||||
elseif (!$this->userAuth instanceof UserAuthenticationInterface) {
|
||||
$uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
|
||||
$form_state->set('uid', $uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user was not authenticated, or if too many logins were attempted.
|
||||
*
|
||||
* This validation function should always be the last one.
|
||||
*/
|
||||
public function validateFinal(array &$form, FormStateInterface $form_state) {
|
||||
$flood_config = $this->config('user.flood');
|
||||
if (!$form_state->get('uid')) {
|
||||
// Always register an IP-based failed login event.
|
||||
$this->userFloodControl->register('user.failed_login_ip', $flood_config->get('ip_window'));
|
||||
// Register a per-user failed login event.
|
||||
if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
|
||||
$this->userFloodControl->register('user.failed_login_user', $flood_config->get('user_window'), $flood_control_user_identifier);
|
||||
}
|
||||
|
||||
if ($flood_control_triggered = $form_state->get('flood_control_triggered')) {
|
||||
if ($flood_control_triggered == 'user') {
|
||||
$message = $this->formatPlural($flood_config->get('user_limit'), 'There has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', 'There have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')->toString()]);
|
||||
}
|
||||
else {
|
||||
// We did not find a uid, so the limit is IP-based.
|
||||
$message = $this->t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')->toString()]);
|
||||
}
|
||||
$response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $message], $this->t('Login failed'), 'maintenance_page__flood');
|
||||
$response->setStatusCode(403);
|
||||
$form_state->setResponse($response);
|
||||
}
|
||||
else {
|
||||
$form_state->setErrorByName('name', $this->t('Unrecognized username or password. <a href=":password">Forgot your password?</a>', [':password' => Url::fromRoute('user.pass')->toString()]));
|
||||
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
|
||||
if (!empty($accounts)) {
|
||||
$this->logger('user')->notice('Login attempt failed for %user.', ['%user' => $form_state->getValue('name')]);
|
||||
}
|
||||
else {
|
||||
// If the username entered is not a valid user,
|
||||
// only store the IP address.
|
||||
$this->logger('user')->notice('Login attempt failed from %ip.', ['%ip' => $this->getRequest()->getClientIp()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!$form_state->get('flood_control_skip_clear') && $flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
|
||||
// Clear past failures for this user so as not to block a user who might
|
||||
// log in and out more than once in an hour.
|
||||
$this->userFloodControl->clear('user.failed_login_user', $flood_control_user_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
62
web/core/modules/user/src/Form/UserLogoutConfirm.php
Normal file
62
web/core/modules/user/src/Form/UserLogoutConfirm.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a confirmation form for user logout.
|
||||
*/
|
||||
class UserLogoutConfirm extends ConfirmFormBase implements WorkspaceSafeFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText(): TranslatableMarkup {
|
||||
return $this->t('Log out');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.EmptyString
|
||||
return $this->t('');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion(): TranslatableMarkup {
|
||||
return $this->t('Are you sure you want to log out?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl(): Url {
|
||||
return new Url('<front>');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId(): string {
|
||||
return 'user_logout_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||
user_logout();
|
||||
$form_state->setRedirect('<front>');
|
||||
}
|
||||
|
||||
}
|
||||
232
web/core/modules/user/src/Form/UserMultipleCancelConfirm.php
Normal file
232
web/core/modules/user/src/Form/UserMultipleCancelConfirm.php
Normal file
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a confirmation form for cancelling multiple user accounts.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserMultipleCancelConfirm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The temp store factory.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new UserMultipleCancelConfirm.
|
||||
*
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The temp store factory.
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(PrivateTempStoreFactory $temp_store_factory, UserStorageInterface $user_storage, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->tempStoreFactory = $temp_store_factory;
|
||||
$this->userStorage = $user_storage;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_multiple_cancel_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to cancel these user accounts?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.user.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Retrieve the accounts to be canceled from the temp store.
|
||||
/** @var \Drupal\user\Entity\User[] $accounts */
|
||||
$accounts = $this->tempStoreFactory
|
||||
->get('user_user_operations_cancel')
|
||||
->get($this->currentUser()->id());
|
||||
if (!$accounts) {
|
||||
return $this->redirect('entity.user.collection');
|
||||
}
|
||||
|
||||
$root = NULL;
|
||||
$names = [];
|
||||
$form['accounts'] = ['#tree' => TRUE];
|
||||
foreach ($accounts as $account) {
|
||||
$uid = $account->id();
|
||||
$names[$uid] = $account->label();
|
||||
// Prevent user 1 from being canceled.
|
||||
if ($uid <= 1) {
|
||||
$root = intval($uid) === 1 ? $account : $root;
|
||||
continue;
|
||||
}
|
||||
$form['accounts'][$uid] = [
|
||||
'#type' => 'hidden',
|
||||
'#value' => $uid,
|
||||
];
|
||||
}
|
||||
|
||||
$form['account']['names'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $names,
|
||||
];
|
||||
|
||||
// Output a notice that user 1 cannot be canceled.
|
||||
if (isset($root)) {
|
||||
$redirect = (count($accounts) == 1);
|
||||
$message = $this->t('The user account %name cannot be canceled.', ['%name' => $root->label()]);
|
||||
$this->messenger()->addMessage($message, $redirect ? MessengerInterface::TYPE_ERROR : MessengerInterface::TYPE_WARNING);
|
||||
// If only user 1 was selected, redirect to the overview.
|
||||
if ($redirect) {
|
||||
return $this->redirect('entity.user.collection');
|
||||
}
|
||||
}
|
||||
|
||||
$form['operation'] = ['#type' => 'hidden', '#value' => 'cancel'];
|
||||
|
||||
// Display account cancellation method selection, if allowed.
|
||||
$user = $this->currentUser();
|
||||
$selectCancel = $user->hasPermission('administer users') || $user->hasPermission('select account cancellation method');
|
||||
|
||||
$form['user_cancel_method'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Cancellation method'),
|
||||
'#access' => $selectCancel,
|
||||
];
|
||||
|
||||
$form['user_cancel_method'] += user_cancel_methods();
|
||||
|
||||
if (!$selectCancel) {
|
||||
// Display an item to inform the user of the setting.
|
||||
$default_method = $form['user_cancel_method']['#default_value'];
|
||||
$form['user_cancel_method_show'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('When cancelling these accounts'),
|
||||
'#plain_text' => $form['user_cancel_method']['#options'][$default_method],
|
||||
];
|
||||
}
|
||||
|
||||
// Allow to send the account cancellation confirmation mail.
|
||||
$form['user_cancel_confirm'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Require email confirmation'),
|
||||
'#default_value' => FALSE,
|
||||
'#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'),
|
||||
];
|
||||
// Also allow to send account canceled notification mail, if enabled.
|
||||
$form['user_cancel_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is canceled'),
|
||||
'#default_value' => FALSE,
|
||||
'#access' => $this->config('user.settings')->get('notify.status_canceled'),
|
||||
'#description' => $this->t('When enabled, the user will receive an email notification after the account has been canceled.'),
|
||||
];
|
||||
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$current_user_id = $this->currentUser()->id();
|
||||
|
||||
// Clear out the accounts from the temp store.
|
||||
$this->tempStoreFactory->get('user_user_operations_cancel')->delete($current_user_id);
|
||||
if ($form_state->getValue('confirm')) {
|
||||
foreach ($form_state->getValue('accounts') as $uid => $value) {
|
||||
// Prevent programmatic form submissions from cancelling user 1.
|
||||
if ($uid <= 1) {
|
||||
continue;
|
||||
}
|
||||
// Prevent user administrators from deleting themselves without
|
||||
// confirmation.
|
||||
if ($uid == $current_user_id) {
|
||||
$admin_form_mock = [];
|
||||
$admin_form_state = $form_state;
|
||||
$admin_form_state->unsetValue('user_cancel_confirm');
|
||||
// The $user global is not a complete user entity, so load the full
|
||||
// entity.
|
||||
$account = $this->userStorage->load($uid);
|
||||
$admin_form = $this->entityTypeManager->getFormObject('user', 'cancel');
|
||||
$admin_form->setEntity($account);
|
||||
// Calling this directly required to init form object with $account.
|
||||
$admin_form->buildForm($admin_form_mock, $admin_form_state);
|
||||
$admin_form->submitForm($admin_form_mock, $admin_form_state);
|
||||
}
|
||||
else {
|
||||
user_cancel($form_state->getValues(), $uid, $form_state->getValue('user_cancel_method'));
|
||||
}
|
||||
}
|
||||
}
|
||||
$form_state->setRedirect('entity.user.collection');
|
||||
}
|
||||
|
||||
}
|
||||
224
web/core/modules/user/src/Form/UserPasswordForm.php
Normal file
224
web/core/modules/user/src/Form/UserPasswordForm.php
Normal file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Component\Utility\EmailValidatorInterface;
|
||||
use Drupal\Core\Config\ConfigFactory;
|
||||
use Drupal\Core\Flood\FloodInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Render\Element\Email;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Drupal\user\UserNameValidator;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a user password reset form.
|
||||
*
|
||||
* Send the user an email to reset their password.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserPasswordForm extends FormBase implements WorkspaceSafeFormInterface {
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The flood service.
|
||||
*
|
||||
* @var \Drupal\Core\Flood\FloodInterface
|
||||
*/
|
||||
protected $flood;
|
||||
|
||||
/**
|
||||
* The email validator service.
|
||||
*
|
||||
* @var \Drupal\Component\Utility\EmailValidatorInterface
|
||||
*/
|
||||
protected $emailValidator;
|
||||
|
||||
/**
|
||||
* Constructs a UserPasswordForm object.
|
||||
*
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactory $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\Flood\FloodInterface $flood
|
||||
* The flood service.
|
||||
* @param \Drupal\user\UserNameValidator $userNameValidator
|
||||
* The user validator service.
|
||||
* @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
|
||||
* The email validator service.
|
||||
*/
|
||||
public function __construct(
|
||||
UserStorageInterface $user_storage,
|
||||
LanguageManagerInterface $language_manager,
|
||||
ConfigFactory $config_factory,
|
||||
FloodInterface $flood,
|
||||
protected UserNameValidator $userNameValidator,
|
||||
EmailValidatorInterface $email_validator,
|
||||
) {
|
||||
$this->userStorage = $user_storage;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->flood = $flood;
|
||||
$this->emailValidator = $email_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory'),
|
||||
$container->get('flood'),
|
||||
$container->get('user.name_validator'),
|
||||
$container->get('email.validator'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_pass';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Username or email address'),
|
||||
'#size' => 60,
|
||||
'#maxlength' => max(UserInterface::USERNAME_MAX_LENGTH, Email::EMAIL_MAX_LENGTH),
|
||||
'#required' => TRUE,
|
||||
'#attributes' => [
|
||||
'autocorrect' => 'off',
|
||||
'autocapitalize' => 'off',
|
||||
'spellcheck' => 'false',
|
||||
'autofocus' => 'autofocus',
|
||||
'autocomplete' => 'username',
|
||||
],
|
||||
];
|
||||
// Allow logged in users to request this also.
|
||||
$user = $this->currentUser();
|
||||
if ($user->isAuthenticated()) {
|
||||
$form['name']['#type'] = 'value';
|
||||
$form['name']['#value'] = $user->getEmail();
|
||||
$form['mail'] = [
|
||||
'#prefix' => '<p>',
|
||||
'#markup' => $this->t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the email.', ['%email' => $user->getEmail()]),
|
||||
'#suffix' => '</p>',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$form['mail'] = [
|
||||
'#prefix' => '<p>',
|
||||
'#markup' => $this->t('Password reset instructions will be sent to your registered email address.'),
|
||||
'#suffix' => '</p>',
|
||||
];
|
||||
$form['name']['#default_value'] = $this->getRequest()->query->get('name');
|
||||
}
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Submit')];
|
||||
$form['#cache']['contexts'][] = 'url.query_args';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$flood_config = $this->configFactory->get('user.flood');
|
||||
if (!$this->flood->isAllowed('user.password_request_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
|
||||
$form_state->setErrorByName('name', $this->t('Too many password recovery requests from your IP address. It is temporarily blocked. Try again later or contact the site administrator.'));
|
||||
return;
|
||||
}
|
||||
$this->flood->register('user.password_request_ip', $flood_config->get('ip_window'));
|
||||
// First, see if the input is possibly valid as a username.
|
||||
$name = trim($form_state->getValue('name'));
|
||||
$violations = $this->userNameValidator->validateName($name);
|
||||
// Usernames have a maximum length shorter than email addresses. Only print
|
||||
// this error if the input is not valid as a username or email address.
|
||||
if ($violations->count() > 0 && !$this->emailValidator->isValid($name)) {
|
||||
$form_state->setErrorByName('name', $this->t("The username or email address is invalid."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load by email.
|
||||
$users = $this->userStorage->loadByProperties(['mail' => $name]);
|
||||
if (empty($users)) {
|
||||
// No success, try to load by name.
|
||||
$users = $this->userStorage->loadByProperties(['name' => $name]);
|
||||
}
|
||||
$account = reset($users);
|
||||
// Blocked accounts cannot request a new password.
|
||||
if ($account && $account->id() && $account->isActive()) {
|
||||
// Register flood events based on the uid only, so they apply for any
|
||||
// IP address. This allows them to be cleared on successful reset (from
|
||||
// any IP).
|
||||
$identifier = $account->id();
|
||||
if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
|
||||
return;
|
||||
}
|
||||
$this->flood->register('user.password_request_user', $flood_config->get('user_window'), $identifier);
|
||||
$form_state->setValueForElement(['#parents' => ['account']], $account);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$account = $form_state->getValue('account');
|
||||
if ($account) {
|
||||
// Mail one time login URL and instructions using current language.
|
||||
$mail = _user_mail_notify('password_reset', $account);
|
||||
if (!empty($mail)) {
|
||||
$this->logger('user')
|
||||
->info('Password reset instructions mailed to %name at %email.', [
|
||||
'%name' => $account->getAccountName(),
|
||||
'%email' => $account->getEmail(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->logger('user')
|
||||
->info('Password reset form was submitted with an unknown or inactive account: %name.', [
|
||||
'%name' => $form_state->getValue('name'),
|
||||
]);
|
||||
}
|
||||
// Make sure the status text is displayed even if no email was sent. This
|
||||
// message is deliberately the same as the success message for privacy.
|
||||
$this->messenger()
|
||||
->addStatus($this->t('If %identifier is a valid account, an email will be sent with instructions to reset your password.', [
|
||||
'%identifier' => $form_state->getValue('name'),
|
||||
]));
|
||||
|
||||
$form_state->setRedirect('<front>');
|
||||
}
|
||||
|
||||
}
|
||||
77
web/core/modules/user/src/Form/UserPasswordResetForm.php
Normal file
77
web/core/modules/user/src/Form/UserPasswordResetForm.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form controller for the user password forms.
|
||||
*
|
||||
* Users followed the link in the email, now they can enter a new password.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserPasswordResetForm extends FormBase implements WorkspaceSafeFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_pass_reset';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @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\Core\Session\AccountInterface $user
|
||||
* User requesting reset.
|
||||
* @param string $expiration_date
|
||||
* Formatted expiration date for the login link, or NULL if the link does
|
||||
* not expire.
|
||||
* @param int $timestamp
|
||||
* The current timestamp.
|
||||
* @param string $hash
|
||||
* Login link hash.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?AccountInterface $user = NULL, $expiration_date = NULL, $timestamp = NULL, $hash = NULL) {
|
||||
if ($expiration_date) {
|
||||
$form['message'] = ['#markup' => $this->t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to log in to the site and change your password.</p>', ['%user_name' => $user->getAccountName(), '%expiration_date' => $expiration_date])];
|
||||
$form['#title'] = $this->t('Reset password');
|
||||
}
|
||||
else {
|
||||
// No expiration for first time login.
|
||||
$form['message'] = ['#markup' => $this->t('<p>This is a one-time login for %user_name.</p><p>Click on this button to log in to the site and change your password.</p>', ['%user_name' => $user->getAccountName()])];
|
||||
$form['#title'] = $this->t('Set password');
|
||||
}
|
||||
|
||||
$form['help'] = ['#markup' => '<p>' . $this->t('This login can be used only once.') . '</p>'];
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Log in'),
|
||||
];
|
||||
$form['#action'] = Url::fromRoute('user.reset.login', [
|
||||
'uid' => $user->id(),
|
||||
'timestamp' => $timestamp,
|
||||
'hash' => $hash,
|
||||
])->toString();
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// This form works by submitting the hash and timestamp to the user.reset
|
||||
// route with a 'login' action.
|
||||
}
|
||||
|
||||
}
|
||||
264
web/core/modules/user/src/Form/UserPermissionsForm.php
Normal file
264
web/core/modules/user/src/Form/UserPermissionsForm.php
Normal file
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\PermissionHandlerInterface;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides the user permissions administration form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserPermissionsForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The permission handler.
|
||||
*
|
||||
* @var \Drupal\user\PermissionHandlerInterface
|
||||
*/
|
||||
protected $permissionHandler;
|
||||
|
||||
/**
|
||||
* The role storage.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorageInterface
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new UserPermissionsForm.
|
||||
*
|
||||
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
|
||||
* The permission handler.
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList|null $moduleExtensionList
|
||||
* The module extension list.
|
||||
*/
|
||||
public function __construct(PermissionHandlerInterface $permission_handler, RoleStorageInterface $role_storage, ModuleHandlerInterface $module_handler, protected ?ModuleExtensionList $moduleExtensionList = NULL) {
|
||||
$this->permissionHandler = $permission_handler;
|
||||
$this->roleStorage = $role_storage;
|
||||
$this->moduleHandler = $module_handler;
|
||||
if ($this->moduleExtensionList === NULL) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() without the $moduleExtensionList argument is deprecated in drupal:10.3.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3310017', E_USER_DEPRECATED);
|
||||
$this->moduleExtensionList = \Drupal::service('extension.list.module');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.permissions'),
|
||||
$container->get('entity_type.manager')->getStorage('user_role'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('extension.list.module'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'user_admin_permissions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the roles to display in this form.
|
||||
*
|
||||
* @return \Drupal\user\RoleInterface[]
|
||||
* An array of role objects.
|
||||
*/
|
||||
protected function getRoles() {
|
||||
return $this->roleStorage->loadMultipleOverrideFree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group permissions by the modules that provide them.
|
||||
*
|
||||
* @return string[][]
|
||||
* A nested array. The outer keys are modules that provide permissions. The
|
||||
* inner arrays are permission names keyed by their machine names.
|
||||
*/
|
||||
protected function permissionsByProvider(): array {
|
||||
$permissions = $this->permissionHandler->getPermissions();
|
||||
$permissions_by_provider = [];
|
||||
foreach ($permissions as $permission_name => $permission) {
|
||||
$permissions_by_provider[$permission['provider']][$permission_name] = $permission;
|
||||
}
|
||||
|
||||
// Move the access content permission to the Node module if it is installed.
|
||||
// @todo Add an alter so that this section can be moved to the Node module.
|
||||
if ($this->moduleHandler->moduleExists('node')) {
|
||||
// Insert 'access content' before the 'view own unpublished content' key
|
||||
// in order to maintain the UI even though the permission is provided by
|
||||
// the system module.
|
||||
$keys = array_keys($permissions_by_provider['node']);
|
||||
$offset = (int) array_search('view own unpublished content', $keys);
|
||||
$permissions_by_provider['node'] = array_merge(
|
||||
array_slice($permissions_by_provider['node'], 0, $offset),
|
||||
['access content' => $permissions_by_provider['system']['access content']],
|
||||
array_slice($permissions_by_provider['node'], $offset)
|
||||
);
|
||||
unset($permissions_by_provider['system']['access content']);
|
||||
}
|
||||
|
||||
return $permissions_by_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$role_names = [];
|
||||
$role_permissions = [];
|
||||
$admin_roles = [];
|
||||
foreach ($this->getRoles() as $role_name => $role) {
|
||||
// Retrieve role names for columns.
|
||||
$role_names[$role_name] = $role->label();
|
||||
// Fetch permissions for the roles.
|
||||
$role_permissions[$role_name] = $role->getPermissions();
|
||||
$admin_roles[$role_name] = $role->isAdmin();
|
||||
}
|
||||
|
||||
// Store $role_names for use when saving the data.
|
||||
$form['role_names'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $role_names,
|
||||
];
|
||||
// Render role/permission overview:
|
||||
$hide_descriptions = system_admin_compact_mode();
|
||||
|
||||
$form['system_compact_link'] = [
|
||||
'#id' => FALSE,
|
||||
'#type' => 'system_compact_link',
|
||||
];
|
||||
|
||||
$form['filters'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => ['table-filter', 'js-show'],
|
||||
],
|
||||
];
|
||||
|
||||
$form['filters']['text'] = [
|
||||
'#type' => 'search',
|
||||
'#title' => $this->t('Filter permissions'),
|
||||
'#title_display' => 'invisible',
|
||||
'#size' => 30,
|
||||
'#placeholder' => $this->t('Filter by permission name'),
|
||||
'#description' => $this->t('Enter permission name'),
|
||||
'#attributes' => [
|
||||
'class' => ['table-filter-text'],
|
||||
'data-table' => '#permissions',
|
||||
'autocomplete' => 'off',
|
||||
],
|
||||
];
|
||||
|
||||
$form['permissions'] = [
|
||||
'#type' => 'table',
|
||||
'#empty' => $this->t('No permissions found.'),
|
||||
'#header' => [$this->t('Permission')],
|
||||
'#id' => 'permissions',
|
||||
'#attributes' => ['class' => ['permissions', 'js-permissions']],
|
||||
'#sticky' => TRUE,
|
||||
];
|
||||
foreach ($role_names as $name) {
|
||||
$form['permissions']['#header'][] = [
|
||||
'data' => $name,
|
||||
'class' => ['checkbox'],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->permissionsByProvider() as $provider => $permissions) {
|
||||
// Module name.
|
||||
$form['permissions']['module_' . $provider] = [
|
||||
[
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => count($role_names) + 1,
|
||||
'class' => ['module'],
|
||||
'id' => 'module-' . $provider,
|
||||
],
|
||||
'#markup' => $this->moduleExtensionList->getName($provider),
|
||||
],
|
||||
];
|
||||
foreach ($permissions as $perm => $perm_item) {
|
||||
// Fill in default values for the permission.
|
||||
$perm_item += [
|
||||
'description' => '',
|
||||
'restrict access' => FALSE,
|
||||
'warning' => !empty($perm_item['restrict access']) ? $this->t('Warning: Give to trusted roles only; this permission has security implications.') : '',
|
||||
];
|
||||
$form['permissions'][$perm]['description'] = [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<div class="permission"><span class="title table-filter-text-source">{{ title }}</span>{% if description or warning %}<div class="description">{% if warning %}<em class="permission-warning">{{ warning }}</em> {% endif %}{{ description }}</div>{% endif %}</div>',
|
||||
'#context' => [
|
||||
'title' => $perm_item['title'],
|
||||
],
|
||||
];
|
||||
// Show the permission description.
|
||||
if (!$hide_descriptions) {
|
||||
$form['permissions'][$perm]['description']['#context']['description'] = $perm_item['description'];
|
||||
$form['permissions'][$perm]['description']['#context']['warning'] = $perm_item['warning'];
|
||||
}
|
||||
foreach ($role_names as $rid => $name) {
|
||||
$form['permissions'][$perm][$rid] = [
|
||||
'#title' => $name . ': ' . $perm_item['title'],
|
||||
'#title_display' => 'invisible',
|
||||
'#wrapper_attributes' => [
|
||||
'class' => ['checkbox'],
|
||||
],
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => in_array($perm, $role_permissions[$rid]) ? 1 : 0,
|
||||
'#attributes' => ['class' => ['rid-' . $rid, 'js-rid-' . $rid]],
|
||||
'#parents' => [$rid, $perm],
|
||||
];
|
||||
// Show a column of disabled but checked checkboxes.
|
||||
if ($admin_roles[$rid]) {
|
||||
$form['permissions'][$perm][$rid]['#disabled'] = TRUE;
|
||||
$form['permissions'][$perm][$rid]['#default_value'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save permissions'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
|
||||
$form['#attached']['library'][] = 'user/drupal.user.permissions';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
foreach ($form_state->getValue('role_names') as $role_name => $name) {
|
||||
user_role_change_permissions($role_name, (array) $form_state->getValue($role_name));
|
||||
}
|
||||
|
||||
$this->messenger()->addStatus($this->t('The changes have been saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides the user permissions administration form for one or more module(s).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserPermissionsModuleSpecificForm extends UserPermissionsForm {
|
||||
|
||||
/**
|
||||
* The module list.
|
||||
*
|
||||
* A keyed array of module machine names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $moduleList;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function permissionsByProvider(): array {
|
||||
return array_intersect_key(
|
||||
parent::permissionsByProvider(),
|
||||
array_flip($this->moduleList)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the user permissions administration form for a specific module(s).
|
||||
*
|
||||
* @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 string $modules
|
||||
* (optional) One or more module machine names, comma-separated.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $modules = ''): array {
|
||||
$this->moduleList = explode(',', $modules);
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that at least one module defines permissions.
|
||||
*
|
||||
* @param string $modules
|
||||
* (optional) One or more module machine names, comma-separated.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access($modules): AccessResultInterface {
|
||||
foreach (explode(',', $modules) as $module) {
|
||||
if ($this->permissionHandler->moduleProvidesPermissions($module)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Provides the user permissions administration form for a specific role.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class UserPermissionsRoleSpecificForm extends UserPermissionsForm {
|
||||
|
||||
/**
|
||||
* The specific role for this form.
|
||||
*
|
||||
* @var \Drupal\user\RoleInterface
|
||||
*/
|
||||
protected $userRole;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getRoles() {
|
||||
return [$this->userRole->id() => $this->userRole];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the user permissions administration form for a specific role.
|
||||
*
|
||||
* @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;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
529
web/core/modules/user/src/Hook/UserHooks.php
Normal file
529
web/core/modules/user/src/Hook/UserHooks.php
Normal file
@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Hook;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\filter\FilterFormatInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\image\Plugin\Field\FieldType\ImageItem;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\Core\Asset\AttachedAssetsInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for user.
|
||||
*/
|
||||
class UserHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.user':
|
||||
$output = '';
|
||||
$output .= '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->t('The User module allows users to register, log in, and log out. It also allows users with proper permissions to manage user roles and permissions. For more information, see the <a href=":user_docs">online documentation for the User module</a>.', [':user_docs' => 'https://www.drupal.org/documentation/modules/user']) . '</p>';
|
||||
$output .= '<h2>' . $this->t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Creating and managing users') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('Through the <a href=":people">People administration page</a> you can add and cancel user accounts and assign users to roles. By editing one particular user you can change their username, email address, password, and information in other fields.', [':people' => Url::fromRoute('entity.user.collection')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Configuring user roles') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('<em>Roles</em> are used to group and classify users; each user can be assigned one or more roles. Typically there are two pre-defined roles: <em>Anonymous user</em> (users that are not logged in), and <em>Authenticated user</em> (users that are registered and logged in). Depending on how your site was set up, an <em>Administrator</em> role may also be available: users with this role will automatically be assigned any new permissions whenever a module is installed. You can create additional roles on the <a href=":roles">Roles administration page</a>.', [
|
||||
':roles' => Url::fromRoute('entity.user_role.collection')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Setting permissions') . '</dt>';
|
||||
$output .= '<dd>' . $this->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' => Url::fromRoute('user.admin_permissions')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Other permissions pages') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The main Permissions page can be overwhelming, so each module that defines permissions has its own page for setting them. There are links to these pages on the <a href=":modules">Extend page</a>. When editing a content type, vocabulary, etc., there is also a Manage permissions tab for permissions related to that configuration.', [':modules' => Url::fromRoute('system.modules_list')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Managing account settings') . '</dt>';
|
||||
$output .= '<dd>' . $this->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 installed (the Administrator role).', [':accounts' => Url::fromRoute('entity.user.admin_form')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Managing user account fields') . '</dt>';
|
||||
$output .= '<dd>' . $this->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') ? Url::fromRoute('help.page', [
|
||||
'name' => 'field',
|
||||
])->toString() : '#',
|
||||
':accounts' => Url::fromRoute('entity.user.admin_form')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'user.admin_create':
|
||||
return '<p>' . $this->t("This web page allows administrators to register new users. Users' email addresses and usernames must be unique.") . '</p>';
|
||||
|
||||
case 'user.admin_permissions':
|
||||
return '<p>' . $this->t('Permissions let you control what users can do and see on your site. You can define a specific set of permissions for each role. (See the <a href=":role">Roles</a> page to create a role.) Any permissions granted to the Authenticated user role will be given to any user who is logged in to your site. On the <a href=":settings">Role settings</a> page, you can make any role into an Administrator role for the site, meaning that role will be granted all permissions. You should be careful to ensure that only trusted users are given this access and level of control of your site.', [
|
||||
':role' => Url::fromRoute('entity.user_role.collection')->toString(),
|
||||
':settings' => Url::fromRoute('user.role.settings')->toString(),
|
||||
]) . '</p>';
|
||||
|
||||
case 'entity.user_role.collection':
|
||||
return '<p>' . $this->t('A role defines a group of users that have certain privileges. These privileges are defined on the <a href=":permissions">Permissions page</a>. Here, you can define the names and the display sort order of the roles on your site. It is recommended to order roles from least permissive (for example, Anonymous user) to most permissive (for example, Administrator user). Users who are not logged in have the Anonymous user role. Users who are logged in have the Authenticated user role, plus any other roles granted to their user account.', [
|
||||
':permissions' => Url::fromRoute('user.admin_permissions')->toString(),
|
||||
]) . '</p>';
|
||||
|
||||
case 'entity.user.field_ui_fields':
|
||||
return '<p>' . $this->t('This form lets administrators add and edit fields for storing user data.') . '</p>';
|
||||
|
||||
case 'entity.entity_form_display.user.default':
|
||||
return '<p>' . $this->t('This form lets administrators configure how form fields should be displayed when editing a user profile.') . '</p>';
|
||||
|
||||
case 'entity.entity_view_display.user.default':
|
||||
return '<p>' . $this->t('This form lets administrators configure how fields should be displayed when rendering a user profile page.') . '</p>';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
#[Hook('theme')]
|
||||
public function theme() : array {
|
||||
return [
|
||||
'user' => [
|
||||
'render element' => 'elements',
|
||||
],
|
||||
'username' => [
|
||||
'variables' => [
|
||||
'account' => NULL,
|
||||
'attributes' => [],
|
||||
'link_options' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_js_settings_alter().
|
||||
*/
|
||||
#[Hook('js_settings_alter')]
|
||||
public function jsSettingsAlter(&$settings, AttachedAssetsInterface $assets): void {
|
||||
// Provide the user ID in drupalSettings to allow JavaScript code to
|
||||
// customize the experience for the end user, rather than the server side,
|
||||
// which would break the render cache.
|
||||
// Similarly, provide a permissions hash, so that permission-dependent data
|
||||
// can be reliably cached on the client side.
|
||||
$user = \Drupal::currentUser();
|
||||
$settings['user']['uid'] = $user->id();
|
||||
$settings['user']['permissionsHash'] = \Drupal::service('user_permissions_hash_generator')->generate($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
#[Hook('entity_extra_field_info')]
|
||||
public function entityExtraFieldInfo(): array {
|
||||
$fields['user']['user']['form']['account'] = [
|
||||
'label' => $this->t('User name and password'),
|
||||
'description' => $this->t('User module account form elements.'),
|
||||
'weight' => -10,
|
||||
];
|
||||
$fields['user']['user']['form']['language'] = [
|
||||
'label' => $this->t('Language settings'),
|
||||
'description' => $this->t('User module form element.'),
|
||||
'weight' => 0,
|
||||
];
|
||||
if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
|
||||
$fields['user']['user']['form']['timezone'] = [
|
||||
'label' => $this->t('Timezone'),
|
||||
'description' => $this->t('System module form element.'),
|
||||
'weight' => 6,
|
||||
];
|
||||
}
|
||||
$fields['user']['user']['display']['member_for'] = [
|
||||
'label' => $this->t('Member for'),
|
||||
'description' => $this->t("User module 'member for' view element."),
|
||||
'weight' => 5,
|
||||
];
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_presave() for user entities.
|
||||
*
|
||||
* @todo https://www.drupal.org/project/drupal/issues/3112704 Move to
|
||||
* \Drupal\user\Entity\User::preSave().
|
||||
*/
|
||||
#[Hook('user_presave')]
|
||||
public function userPresave(UserInterface $account): void {
|
||||
$config = \Drupal::config('system.date');
|
||||
if ($config->get('timezone.user.configurable') && !$account->getTimeZone() && !$config->get('timezone.user.default')) {
|
||||
$account->timezone = $config->get('timezone.default');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_view() for user entities.
|
||||
*/
|
||||
#[Hook('user_view')]
|
||||
public function userView(array &$build, UserInterface $account, EntityViewDisplayInterface $display): void {
|
||||
if ($account->isAuthenticated() && $display->getComponent('member_for')) {
|
||||
$build['member_for'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => '<h4 class="label">' . $this->t('Member for') . '</h4> ' . \Drupal::service('date.formatter')->formatTimeDiffSince($account->getCreatedTime()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_view_alter() for user entities.
|
||||
*
|
||||
* This function adds a default alt tag to the user_picture field to maintain
|
||||
* accessibility.
|
||||
*/
|
||||
#[Hook('user_view_alter')]
|
||||
public function userViewAlter(array &$build, UserInterface $account, EntityViewDisplayInterface $display): void {
|
||||
if (!empty($build['user_picture']) && user_picture_enabled()) {
|
||||
foreach (Element::children($build['user_picture']) as $key) {
|
||||
if (!isset($build['user_picture'][$key]['#item']) || !$build['user_picture'][$key]['#item'] instanceof ImageItem) {
|
||||
// User picture field is provided by standard profile install. If the
|
||||
// display is configured to use a different formatter, the #item
|
||||
// render key may not exist, or may not be an image field.
|
||||
continue;
|
||||
}
|
||||
/** @var \Drupal\image\Plugin\Field\FieldType\ImageItem $item */
|
||||
$item = $build['user_picture'][$key]['#item'];
|
||||
if (!$item->get('alt')->getValue()) {
|
||||
$item->get('alt')->setValue(\Drupal::translation()->translate('Profile picture for user @username', ['@username' => $account->getAccountName()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_template_preprocess_default_variables_alter().
|
||||
*
|
||||
* @see user_user_login()
|
||||
* @see user_user_logout()
|
||||
*/
|
||||
#[Hook('template_preprocess_default_variables_alter')]
|
||||
public function templatePreprocessDefaultVariablesAlter(&$variables): void {
|
||||
$user = \Drupal::currentUser();
|
||||
$variables['user'] = clone $user;
|
||||
// Remove password and session IDs, since themes should not need nor see
|
||||
// them.
|
||||
unset($variables['user']->pass, $variables['user']->sid, $variables['user']->ssid);
|
||||
$variables['is_admin'] = $user->hasPermission('access administration pages');
|
||||
$variables['logged_in'] = $user->isAuthenticated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_login().
|
||||
*/
|
||||
#[Hook('user_login')]
|
||||
public function userLogin(UserInterface $account): void {
|
||||
// Reset default theme variables.
|
||||
\Drupal::service('theme.manager')->resetActiveTheme();
|
||||
|
||||
// If the user has a NULL time zone, notify them to set a time zone.
|
||||
$config = \Drupal::config('system.date');
|
||||
if (!$account->getTimezone() && $config->get('timezone.user.configurable') && $config->get('timezone.user.warn')) {
|
||||
\Drupal::messenger()->addStatus($this->t('Configure your <a href=":user-edit">account time zone setting</a>.', [
|
||||
':user-edit' => $account->toUrl('edit-form', [
|
||||
'query' => \Drupal::destination()->getAsArray(),
|
||||
'fragment' => 'edit-timezone',
|
||||
])->toString(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_logout().
|
||||
*/
|
||||
#[Hook('user_logout')]
|
||||
public function userLogout(AccountInterface $account): void {
|
||||
// Reset default theme variables.
|
||||
\Drupal::service('theme.manager')->resetActiveTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_mail().
|
||||
*/
|
||||
#[Hook('mail')]
|
||||
public function mail($key, &$message, $params): void {
|
||||
$token_service = \Drupal::token();
|
||||
$language_manager = \Drupal::languageManager();
|
||||
$langcode = $message['langcode'];
|
||||
$variables = ['user' => $params['account']];
|
||||
$language = $language_manager->getLanguage($langcode);
|
||||
$original_language = $language_manager->getConfigOverrideLanguage();
|
||||
$language_manager->setConfigOverrideLanguage($language);
|
||||
$mail_config = \Drupal::config('user.mail');
|
||||
$token_options = ['langcode' => $langcode, 'callback' => 'user_mail_tokens', 'clear' => TRUE];
|
||||
$message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($mail_config->get($key . '.subject'), $variables, $token_options));
|
||||
$message['body'][] = $token_service->replacePlain($mail_config->get($key . '.body'), $variables, $token_options);
|
||||
$language_manager->setConfigOverrideLanguage($original_language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_insert() for user_role entities.
|
||||
*/
|
||||
#[Hook('user_role_insert')]
|
||||
public function userRoleInsert(RoleInterface $role): void {
|
||||
// Ignore the authenticated and anonymous roles or the role is being synced.
|
||||
if (in_array($role->id(), [
|
||||
RoleInterface::AUTHENTICATED_ID,
|
||||
RoleInterface::ANONYMOUS_ID,
|
||||
]) || $role->isSyncing()) {
|
||||
return;
|
||||
}
|
||||
assert(Inspector::assertStringable($role->label()), 'Role label is expected to be a string.');
|
||||
$add_id = 'user_add_role_action.' . $role->id();
|
||||
if (!Action::load($add_id)) {
|
||||
$action = Action::create([
|
||||
'id' => $add_id,
|
||||
'type' => 'user',
|
||||
'label' => $this->t('Add the @label role to the selected user(s)', [
|
||||
'@label' => $role->label(),
|
||||
]),
|
||||
'configuration' => [
|
||||
'rid' => $role->id(),
|
||||
],
|
||||
'plugin' => 'user_add_role_action',
|
||||
]);
|
||||
$action->trustData()->save();
|
||||
}
|
||||
$remove_id = 'user_remove_role_action.' . $role->id();
|
||||
if (!Action::load($remove_id)) {
|
||||
$action = Action::create([
|
||||
'id' => $remove_id,
|
||||
'type' => 'user',
|
||||
'label' => $this->t('Remove the @label role from the selected user(s)', [
|
||||
'@label' => $role->label(),
|
||||
]),
|
||||
'configuration' => [
|
||||
'rid' => $role->id(),
|
||||
],
|
||||
'plugin' => 'user_remove_role_action',
|
||||
]);
|
||||
$action->trustData()->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for user_role entities.
|
||||
*/
|
||||
#[Hook('user_role_delete')]
|
||||
public function userRoleDelete(RoleInterface $role): void {
|
||||
// Delete role references for all users.
|
||||
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
|
||||
$user_storage->deleteRoleReferences([$role->id()]);
|
||||
// Ignore the authenticated and anonymous roles or the role is being synced.
|
||||
if (in_array($role->id(), [
|
||||
RoleInterface::AUTHENTICATED_ID,
|
||||
RoleInterface::ANONYMOUS_ID,
|
||||
]) || $role->isSyncing()) {
|
||||
return;
|
||||
}
|
||||
$actions = Action::loadMultiple(['user_add_role_action.' . $role->id(), 'user_remove_role_action.' . $role->id()]);
|
||||
foreach ($actions as $action) {
|
||||
$action->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_element_info_alter().
|
||||
*/
|
||||
#[Hook('element_info_alter')]
|
||||
public function elementInfoAlter(array &$types): void {
|
||||
if (isset($types['password_confirm'])) {
|
||||
$types['password_confirm']['#process'][] = 'user_form_process_password_confirm';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_modules_uninstalled().
|
||||
*/
|
||||
#[Hook('modules_uninstalled')]
|
||||
public function modulesUninstalled($modules): void {
|
||||
// Remove any potentially orphan module data stored for users.
|
||||
\Drupal::service('user.data')->delete($modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar().
|
||||
*/
|
||||
#[Hook('toolbar')]
|
||||
public function toolbar(): array {
|
||||
$user = \Drupal::currentUser();
|
||||
$items['user'] = [
|
||||
'#type' => 'toolbar_item',
|
||||
'tab' => [
|
||||
'#type' => 'link',
|
||||
'#title' => $user->getDisplayName(),
|
||||
'#url' => Url::fromRoute('user.page'),
|
||||
'#attributes' => [
|
||||
'title' => $this->t('My account'),
|
||||
'class' => [
|
||||
'toolbar-icon',
|
||||
'toolbar-icon-user',
|
||||
],
|
||||
],
|
||||
'#cache' => [
|
||||
// Vary cache for anonymous and authenticated users.
|
||||
'contexts' => [
|
||||
'user.roles:anonymous',
|
||||
],
|
||||
],
|
||||
],
|
||||
'tray' => [
|
||||
'#heading' => $this->t('User account actions'),
|
||||
],
|
||||
'#weight' => 100,
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'user/drupal.user.icons',
|
||||
],
|
||||
],
|
||||
];
|
||||
if ($user->isAnonymous()) {
|
||||
$links = [
|
||||
'login' => [
|
||||
'title' => $this->t('Log in'),
|
||||
'url' => Url::fromRoute('user.page'),
|
||||
],
|
||||
];
|
||||
$items['user']['tray']['user_links'] = [
|
||||
'#theme' => 'links__toolbar_user',
|
||||
'#links' => $links,
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'toolbar-menu',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$items['user']['tab']['#title'] = [
|
||||
'#lazy_builder' => [
|
||||
'user.toolbar_link_builder:renderDisplayName',
|
||||
[],
|
||||
],
|
||||
'#create_placeholder' => TRUE,
|
||||
'#lazy_builder_preview' => [
|
||||
// Add a line of whitespace to the placeholder to ensure the icon is
|
||||
// positioned in the same place it will be when the lazy loaded content
|
||||
// appears.
|
||||
'#markup' => ' ',
|
||||
],
|
||||
];
|
||||
$items['user']['tray']['user_links'] = [
|
||||
'#lazy_builder' => [
|
||||
'user.toolbar_link_builder:renderToolbarLinks',
|
||||
[],
|
||||
],
|
||||
'#create_placeholder' => TRUE,
|
||||
'#lazy_builder_preview' => [
|
||||
'#markup' => '<a href="#" class="toolbar-tray-lazy-placeholder-link"> </a>',
|
||||
],
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter() for \Drupal\system\Form\RegionalForm.
|
||||
*/
|
||||
#[Hook('form_system_regional_settings_alter')]
|
||||
public function formSystemRegionalSettingsAlter(&$form, FormStateInterface $form_state) : void {
|
||||
$config = \Drupal::config('system.date');
|
||||
$form['timezone']['configurable_timezones'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Users may set their own time zone'),
|
||||
'#default_value' => $config->get('timezone.user.configurable'),
|
||||
];
|
||||
$form['timezone']['configurable_timezones_wrapper'] = [
|
||||
'#type' => 'container',
|
||||
'#states' => [
|
||||
// Hide the user configured timezone settings when users are forced to
|
||||
// use the default setting.
|
||||
'invisible' => [
|
||||
'input[name="configurable_timezones"]' => [
|
||||
'checked' => FALSE,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$form['timezone']['configurable_timezones_wrapper']['empty_timezone_message'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Remind users at login if their time zone is not set'),
|
||||
'#default_value' => $config->get('timezone.user.warn'),
|
||||
'#description' => $this->t('Only applied if users may set their own time zone.'),
|
||||
];
|
||||
$form['timezone']['configurable_timezones_wrapper']['user_default_timezone'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Time zone for new users'),
|
||||
'#default_value' => $config->get('timezone.user.default'),
|
||||
'#options' => [
|
||||
UserInterface::TIMEZONE_DEFAULT => $this->t('Default time zone'),
|
||||
UserInterface::TIMEZONE_EMPTY => $this->t('Empty time zone'),
|
||||
UserInterface::TIMEZONE_SELECT => $this->t('Users may set their own time zone at registration'),
|
||||
],
|
||||
'#description' => $this->t('Only applied if users may set their own time zone.'),
|
||||
];
|
||||
$form['#submit'][] = 'user_form_system_regional_settings_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_filter_format_disable().
|
||||
*/
|
||||
#[Hook('filter_format_disable')]
|
||||
public function filterFormatDisable(FilterFormatInterface $filter_format): void {
|
||||
// Remove the permission from any roles.
|
||||
$permission = $filter_format->getPermissionName();
|
||||
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $role_storage */
|
||||
$role_storage = \Drupal::entityTypeManager()->getStorage('user_role');
|
||||
|
||||
/** @var \Drupal\user\Entity\Role $role */
|
||||
foreach ($role_storage->loadMultipleOverrideFree() as $role) {
|
||||
if ($role->hasPermission($permission)) {
|
||||
$role->revokePermission($permission)->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_operation().
|
||||
*/
|
||||
#[Hook('entity_operation')]
|
||||
public function entityOperation(EntityInterface $entity): array {
|
||||
// Add Manage permissions link if this entity type defines the permissions
|
||||
// link template.
|
||||
if (!$entity->hasLinkTemplate('entity-permissions-form')) {
|
||||
return [];
|
||||
}
|
||||
$bundle_entity_type = $entity->bundle();
|
||||
$route = "entity.{$bundle_entity_type}.entity_permissions_form";
|
||||
if (empty(\Drupal::service('router.route_provider')->getRoutesByNames([$route]))) {
|
||||
return [];
|
||||
}
|
||||
$url = Url::fromRoute($route, [$bundle_entity_type => $entity->id()]);
|
||||
if (!$url->access()) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'manage-permissions' => [
|
||||
'title' => $this->t('Manage permissions'),
|
||||
'weight' => 50,
|
||||
'url' => $url,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
69
web/core/modules/user/src/Hook/UserRequirements.php
Normal file
69
web/core/modules/user/src/Hook/UserRequirements.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user\Hook;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Requirements for the User module.
|
||||
*/
|
||||
class UserRequirements {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
public function __construct(
|
||||
protected readonly EntityTypeManagerInterface $entityTypeManager,
|
||||
protected readonly Connection $connection,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Implements hook_runtime_requirements().
|
||||
*/
|
||||
#[Hook('runtime_requirements')]
|
||||
public function runtime(): array {
|
||||
$requirements = [];
|
||||
|
||||
$result = (bool) $this->entityTypeManager->getStorage('user')->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('uid', 0)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
|
||||
if ($result === FALSE) {
|
||||
$requirements['anonymous user'] = [
|
||||
'title' => $this->t('Anonymous user'),
|
||||
'description' => $this->t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [
|
||||
':url' => 'https://www.drupal.org/node/1029506',
|
||||
]),
|
||||
'severity' => RequirementSeverity::Warning,
|
||||
];
|
||||
}
|
||||
|
||||
$query = $this->connection->select('users_field_data');
|
||||
$query->addExpression('LOWER(mail)', 'lower_mail');
|
||||
$query->isNotNull('mail');
|
||||
$query->groupBy('lower_mail');
|
||||
$query->groupBy('langcode');
|
||||
$query->having('COUNT(uid) > :matches', [':matches' => 1]);
|
||||
$conflicts = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
if ($conflicts > 0) {
|
||||
$requirements['conflicting emails'] = [
|
||||
'title' => $this->t('Conflicting user emails'),
|
||||
'description' => $this->t('Some user accounts have email addresses that differ only by case. For example, one account might have alice@example.com and another might have Alice@Example.com. See <a href=":url">Conflicting User Emails</a> for more information.', [
|
||||
':url' => 'https://www.drupal.org/node/3486109',
|
||||
]),
|
||||
'severity' => RequirementSeverity::Warning,
|
||||
];
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
}
|
||||
155
web/core/modules/user/src/Hook/UserTokensHooks.php
Normal file
155
web/core/modules/user/src/Hook/UserTokensHooks.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Hook;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for user.
|
||||
*/
|
||||
class UserTokensHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
#[Hook('token_info')]
|
||||
public function tokenInfo(): array {
|
||||
$types['user'] = [
|
||||
'name' => $this->t('Users'),
|
||||
'description' => $this->t('Tokens related to individual user accounts.'),
|
||||
'needs-data' => 'user',
|
||||
];
|
||||
$types['current-user'] = [
|
||||
'name' => $this->t('Current user'),
|
||||
'description' => $this->t('Tokens related to the currently logged in user.'),
|
||||
'type' => 'user',
|
||||
];
|
||||
$user['uid'] = ['name' => $this->t('User ID'), 'description' => $this->t("The unique ID of the user account.")];
|
||||
$user['uuid'] = ['name' => $this->t('UUID'), 'description' => $this->t("The UUID of the user account.")];
|
||||
$user['name'] = [
|
||||
'name' => $this->t("Deprecated: User Name"),
|
||||
'description' => $this->t("Deprecated: Use account-name or display-name instead."),
|
||||
];
|
||||
$user['account-name'] = [
|
||||
'name' => $this->t("Account Name"),
|
||||
'description' => $this->t("The login name of the user account."),
|
||||
];
|
||||
$user['display-name'] = [
|
||||
'name' => $this->t("Display Name"),
|
||||
'description' => $this->t("The display name of the user account."),
|
||||
];
|
||||
$user['mail'] = [
|
||||
'name' => $this->t("Email"),
|
||||
'description' => $this->t("The email address of the user account."),
|
||||
];
|
||||
$user['url'] = ['name' => $this->t("URL"), 'description' => $this->t("The URL of the account profile page.")];
|
||||
$user['edit-url'] = ['name' => $this->t("Edit URL"), 'description' => $this->t("The URL of the account edit page.")];
|
||||
$user['last-login'] = [
|
||||
'name' => $this->t("Last login"),
|
||||
'description' => $this->t("The date the user last logged in to the site."),
|
||||
'type' => 'date',
|
||||
];
|
||||
$user['created'] = [
|
||||
'name' => $this->t("Created"),
|
||||
'description' => $this->t("The date the user account was created."),
|
||||
'type' => 'date',
|
||||
];
|
||||
return ['types' => $types, 'tokens' => ['user' => $user]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
#[Hook('tokens')]
|
||||
public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
|
||||
$token_service = \Drupal::token();
|
||||
$url_options = ['absolute' => TRUE];
|
||||
if (isset($options['langcode'])) {
|
||||
$url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
|
||||
$langcode = $options['langcode'];
|
||||
}
|
||||
else {
|
||||
$langcode = NULL;
|
||||
}
|
||||
$replacements = [];
|
||||
if ($type == 'user' && !empty($data['user'])) {
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $data['user'];
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
// Basic user account information.
|
||||
case 'uid':
|
||||
// In the case of hook user_presave uid is not set yet.
|
||||
$replacements[$original] = $account->id() ?: $this->t('not yet assigned');
|
||||
break;
|
||||
|
||||
case 'uuid':
|
||||
$replacements[$original] = $account->uuid();
|
||||
break;
|
||||
|
||||
case 'display-name':
|
||||
$replacements[$original] = $account->getDisplayName();
|
||||
if ($account->isAnonymous()) {
|
||||
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
case 'account-name':
|
||||
$display_name = $account->getAccountName();
|
||||
$replacements[$original] = $display_name;
|
||||
if ($account->isAnonymous()) {
|
||||
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mail':
|
||||
$replacements[$original] = $account->getEmail();
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$replacements[$original] = $account->id() ? $account->toUrl('canonical', $url_options)->toString() : $this->t('not yet assigned');
|
||||
break;
|
||||
|
||||
case 'edit-url':
|
||||
$replacements[$original] = $account->id() ? $account->toUrl('edit-form', $url_options)->toString() : $this->t('not yet assigned');
|
||||
break;
|
||||
|
||||
// These tokens are default variations on the chained tokens handled
|
||||
// below.
|
||||
case 'last-login':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = $account->getLastLoginTime() ? \Drupal::service('date.formatter')->format($account->getLastLoginTime(), 'medium', '', NULL, $langcode) : $this->t('never');
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
// In the case of user_presave the created date may not yet be set.
|
||||
$replacements[$original] = $account->getCreatedTime() ? \Drupal::service('date.formatter')->format($account->getCreatedTime(), 'medium', '', NULL, $langcode) : $this->t('not yet created');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($login_tokens = $token_service->findWithPrefix($tokens, 'last-login')) {
|
||||
$replacements += $token_service->generate('date', $login_tokens, ['date' => $account->getLastLoginTime()], $options, $bubbleable_metadata);
|
||||
}
|
||||
if ($registered_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $registered_tokens, ['date' => $account->getCreatedTime()], $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
if ($type == 'current-user') {
|
||||
$account = User::load(\Drupal::currentUser()->id());
|
||||
$bubbleable_metadata->addCacheContexts(['user']);
|
||||
$replacements += $token_service->generate('user', $tokens, ['user' => $account], $options, $bubbleable_metadata);
|
||||
}
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
}
|
||||
23
web/core/modules/user/src/Hook/UserViewsExecutionHooks.php
Normal file
23
web/core/modules/user/src/Hook/UserViewsExecutionHooks.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Hook;
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for user.
|
||||
*/
|
||||
class UserViewsExecutionHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_views_query_substitutions().
|
||||
*
|
||||
* Allow replacement of current user ID so we can cache these queries.
|
||||
*/
|
||||
#[Hook('views_query_substitutions')]
|
||||
public function viewsQuerySubstitutions(ViewExecutable $view): array {
|
||||
return ['***CURRENT_USER***' => \Drupal::currentUser()->id()];
|
||||
}
|
||||
|
||||
}
|
||||
25
web/core/modules/user/src/Hook/UserViewsHooks.php
Normal file
25
web/core/modules/user/src/Hook/UserViewsHooks.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Hook implementations for user.
|
||||
*/
|
||||
class UserViewsHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_views_plugins_argument_validator_alter().
|
||||
*/
|
||||
#[Hook('views_plugins_argument_validator_alter')]
|
||||
public function viewsPluginsArgumentValidatorAlter(array &$plugins): void {
|
||||
$plugins['entity:user']['title'] = $this->t('User ID');
|
||||
$plugins['entity:user']['class'] = 'Drupal\user\Plugin\views\argument_validator\User';
|
||||
$plugins['entity:user']['provider'] = 'user';
|
||||
}
|
||||
|
||||
}
|
||||
61
web/core/modules/user/src/ModulePermissionsLinkHelper.php
Normal file
61
web/core/modules/user/src/ModulePermissionsLinkHelper.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a helper for generating module permissions links.
|
||||
*/
|
||||
class ModulePermissionsLinkHelper {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Constructs a new service instance.
|
||||
*
|
||||
* @param \Drupal\user\PermissionHandlerInterface $permissionHandler
|
||||
* The user permissions handler service.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $accessManager
|
||||
* The access manager service.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
|
||||
* The module handler service.
|
||||
*/
|
||||
public function __construct(
|
||||
protected PermissionHandlerInterface $permissionHandler,
|
||||
protected AccessManagerInterface $accessManager,
|
||||
protected ModuleExtensionList $moduleExtensionList,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generates a link pointing to a given module's permissions page section.
|
||||
*
|
||||
* @param string $module
|
||||
* The module name.
|
||||
* @param string $name
|
||||
* The module display name.
|
||||
*
|
||||
* @return array|null
|
||||
* A module permissions link as a render array or NULL if the module doesn't
|
||||
* expose any permission or the current user cannot access it.
|
||||
*/
|
||||
public function getModulePermissionsLink(string $module, string $name): ?array {
|
||||
if ($this->permissionHandler->moduleProvidesPermissions($module)) {
|
||||
if ($this->accessManager->checkNamedRoute('user.admin_permissions.module', ['modules' => $module])) {
|
||||
$url = new Url('user.admin_permissions.module', ['modules' => $module]);
|
||||
return [
|
||||
'title' => $this->t('Configure @module permissions', ['@module' => $name]),
|
||||
'description' => '',
|
||||
'url' => $url,
|
||||
];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
242
web/core/modules/user/src/PermissionHandler.php
Normal file
242
web/core/modules/user/src/PermissionHandler.php
Normal file
@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Core\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\Core\Utility\CallableResolver;
|
||||
|
||||
/**
|
||||
* Provides the available permissions based on yml files.
|
||||
*
|
||||
* To define permissions you can use a $module.permissions.yml file. This file
|
||||
* defines machine names, human-readable names, restrict access (if required for
|
||||
* security warning), and optionally descriptions for each permission type. The
|
||||
* machine names are the canonical way to refer to permissions for access
|
||||
* checking.
|
||||
*
|
||||
* If your module needs to define dynamic permissions you can use the
|
||||
* permission_callbacks key to declare a callable that will return an array of
|
||||
* permissions, keyed by machine name. Each item in the array can contain the
|
||||
* same keys as an entry in $module.permissions.yml.
|
||||
*
|
||||
* Here is an example from the core filter module (comments have been added):
|
||||
* @code
|
||||
* # The key is the permission machine name, and is required.
|
||||
* administer filters:
|
||||
* # (required) Human readable name of the permission used in the UI.
|
||||
* title: 'Administer text formats and filters'
|
||||
* # (optional) Additional description fo the permission used in the UI.
|
||||
* description: 'Define how text is handled by combining filters into text formats.'
|
||||
* # (optional) Boolean, when set to true a warning about site security will
|
||||
* # be displayed on the Permissions page. Defaults to false.
|
||||
* restrict access: false
|
||||
*
|
||||
* # An array of callables used to generate dynamic permissions.
|
||||
* permission_callbacks:
|
||||
* # The callable should return an associative array with one or more
|
||||
* # permissions. Each permission array can use the same keys as the example
|
||||
* # permission defined above. Additionally, a dependencies key is supported.
|
||||
* # For more information about permission dependencies see
|
||||
* # PermissionHandlerInterface::getPermissions().
|
||||
* - Drupal\filter\FilterPermissions::permissions
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
* @see filter.permissions.yml
|
||||
* @see \Drupal\filter\FilterPermissions
|
||||
* @see user_api
|
||||
*/
|
||||
class PermissionHandler implements PermissionHandlerInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The YAML discovery class to find all .permissions.yml files.
|
||||
*
|
||||
* @var \Drupal\Core\Discovery\YamlDiscovery
|
||||
*/
|
||||
protected $yamlDiscovery;
|
||||
|
||||
/**
|
||||
* The callable resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\CallableResolver
|
||||
*/
|
||||
protected CallableResolver $callableResolver;
|
||||
|
||||
/**
|
||||
* Constructs a new PermissionHandler.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation.
|
||||
* @param \Drupal\Core\Utility\CallableResolver $callable_resolver
|
||||
* The callable resolver.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
|
||||
* The module extension list.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, CallableResolver $callable_resolver, protected ModuleExtensionList $moduleExtensionList) {
|
||||
$this->callableResolver = $callable_resolver;
|
||||
|
||||
// @todo It would be nice if you could pull all module directories from the
|
||||
// container.
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the YAML discovery.
|
||||
*
|
||||
* @return \Drupal\Core\Discovery\YamlDiscovery
|
||||
* The YAML discovery.
|
||||
*/
|
||||
protected function getYamlDiscovery() {
|
||||
if (!isset($this->yamlDiscovery)) {
|
||||
$this->yamlDiscovery = new YamlDiscovery('permissions', $this->moduleHandler->getModuleDirectories());
|
||||
}
|
||||
return $this->yamlDiscovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPermissions() {
|
||||
$all_permissions = $this->buildPermissionsYaml();
|
||||
|
||||
return $this->sortPermissions($all_permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function moduleProvidesPermissions($module_name) {
|
||||
// @todo Static cache this information.
|
||||
// https://www.drupal.org/node/2339487
|
||||
$permissions = $this->getPermissions();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission['provider'] == $module_name) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds all permissions provided by .permissions.yml files.
|
||||
*
|
||||
* @return array[]
|
||||
* An array with the same structure as
|
||||
* PermissionHandlerInterface::getPermissions().
|
||||
*
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
*/
|
||||
protected function buildPermissionsYaml() {
|
||||
$all_permissions = [];
|
||||
$all_callback_permissions = [];
|
||||
|
||||
foreach ($this->getYamlDiscovery()->findAll() as $provider => $permissions) {
|
||||
// The top-level 'permissions_callback' is a list of methods in callable
|
||||
// syntax, see \Drupal\Core\Utility\CallableResolver. These methods
|
||||
// should return an array of permissions in the same structure.
|
||||
if (isset($permissions['permission_callbacks'])) {
|
||||
foreach ($permissions['permission_callbacks'] as $permission_callback) {
|
||||
$callback = $this->callableResolver->getCallableFromDefinition($permission_callback);
|
||||
if ($callback_permissions = call_user_func($callback)) {
|
||||
// Add any callback permissions to the array of permissions. Any
|
||||
// defaults can then get processed below.
|
||||
foreach ($callback_permissions as $name => $callback_permission) {
|
||||
if (!is_array($callback_permission)) {
|
||||
$callback_permission = [
|
||||
'title' => $callback_permission,
|
||||
];
|
||||
}
|
||||
|
||||
$callback_permission += [
|
||||
'description' => NULL,
|
||||
'provider' => $provider,
|
||||
];
|
||||
|
||||
$all_callback_permissions[$name] = $callback_permission;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($permissions['permission_callbacks']);
|
||||
}
|
||||
|
||||
foreach ($permissions as &$permission) {
|
||||
if (!is_array($permission)) {
|
||||
$permission = [
|
||||
'title' => $permission,
|
||||
];
|
||||
}
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$permission['title'] = $this->t($permission['title']);
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$permission['description'] = isset($permission['description']) ? $this->t($permission['description']) : NULL;
|
||||
$permission['provider'] = !empty($permission['provider']) ? $permission['provider'] : $provider;
|
||||
}
|
||||
|
||||
$all_permissions += $permissions;
|
||||
}
|
||||
|
||||
return $all_permissions + $all_callback_permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given permissions by provider name and title.
|
||||
*
|
||||
* @param array $all_permissions
|
||||
* The permissions to be sorted.
|
||||
*
|
||||
* @return array[]
|
||||
* An array with the same structure as
|
||||
* PermissionHandlerInterface::getPermissions().
|
||||
*
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
*/
|
||||
protected function sortPermissions(array $all_permissions = []) {
|
||||
// Get a list of all the modules providing permissions and sort by
|
||||
// display name.
|
||||
$modules = $this->getModuleNames();
|
||||
|
||||
uasort($all_permissions, function (array $permission_a, array $permission_b) use ($modules) {
|
||||
if ($modules[$permission_a['provider']] == $modules[$permission_b['provider']]) {
|
||||
return $permission_a['title'] <=> $permission_b['title'];
|
||||
}
|
||||
else {
|
||||
return $modules[$permission_a['provider']] <=> $modules[$permission_b['provider']];
|
||||
}
|
||||
});
|
||||
return $all_permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all module names.
|
||||
*
|
||||
* @return string[]
|
||||
* Returns the human readable names of all modules keyed by machine name.
|
||||
*/
|
||||
protected function getModuleNames() {
|
||||
$modules = [];
|
||||
foreach (array_keys($this->moduleHandler->getModuleList()) as $module) {
|
||||
$modules[$module] = $this->moduleExtensionList->getName($module);
|
||||
}
|
||||
asort($modules);
|
||||
return $modules;
|
||||
}
|
||||
|
||||
}
|
||||
66
web/core/modules/user/src/PermissionHandlerInterface.php
Normal file
66
web/core/modules/user/src/PermissionHandlerInterface.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user;
|
||||
|
||||
/**
|
||||
* Defines an interface to list available permissions.
|
||||
*/
|
||||
interface PermissionHandlerInterface {
|
||||
|
||||
/**
|
||||
* Gets all available permissions.
|
||||
*
|
||||
* @return array
|
||||
* An array whose keys are permission names and whose corresponding values
|
||||
* are arrays containing the following key-value pairs:
|
||||
* - title: The human-readable name of the permission, to be shown on the
|
||||
* permission administration page. This should be wrapped in the t()
|
||||
* function so it can be translated.
|
||||
* - description: (optional) A description of what the permission does. This
|
||||
* should be wrapped in the t() function so it can be translated.
|
||||
* - restrict access: (optional) A boolean which can be set to TRUE to
|
||||
* indicate that site administrators should restrict access to this
|
||||
* permission to trusted users. This should be used for permissions that
|
||||
* have inherent security risks across a variety of potential use cases
|
||||
* (for example, the "administer filters" and "bypass node access"
|
||||
* permissions provided by Drupal core). When set to TRUE, a standard
|
||||
* warning message defined in user_admin_permissions() will be displayed
|
||||
* with the permission on the permission administration page. Defaults
|
||||
* to FALSE.
|
||||
* - warning: (optional) A translated warning message to display for this
|
||||
* permission on the permission administration page. This warning
|
||||
* overrides the automatic warning generated by 'restrict access' being
|
||||
* set to TRUE. This should rarely be used, since it is important for all
|
||||
* permissions to have a clear, consistent security warning that is the
|
||||
* same across the site. Use the 'description' key instead to provide any
|
||||
* information that is specific to the permission you are defining.
|
||||
* - dependencies: (optional) An array of dependency entities used when
|
||||
* building this permission, structured in the same way as the return
|
||||
* of ConfigEntityInterface::calculateDependencies(). For example, if this
|
||||
* permission was generated as effect of the existence of node type
|
||||
* 'article', then value of the dependency key is:
|
||||
* @code
|
||||
* 'dependencies' => ['config' => ['node.type.article']]
|
||||
* @endcode
|
||||
* The module providing this permission doesn't have to be added as a
|
||||
* dependency. It is automatically parsed, stored and retrieved from the
|
||||
* 'provider' key.
|
||||
* - provider: The provider name of the permission. This is set
|
||||
* automatically to the module that provides the permission.yml file.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
public function getPermissions();
|
||||
|
||||
/**
|
||||
* Determines whether a module provides some permissions.
|
||||
*
|
||||
* @param string $module_name
|
||||
* The module name.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the module provides some permissions, otherwise FALSE.
|
||||
*/
|
||||
public function moduleProvidesPermissions($module_name);
|
||||
|
||||
}
|
||||
32
web/core/modules/user/src/Plugin/Action/AddRoleUser.php
Normal file
32
web/core/modules/user/src/Plugin/Action/AddRoleUser.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Adds a role to a user.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'user_add_role_action',
|
||||
label: new TranslatableMarkup('Add a role to the selected users'),
|
||||
type: 'user'
|
||||
)]
|
||||
class AddRoleUser extends ChangeUserRoleBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($account = NULL) {
|
||||
$rid = $this->configuration['rid'];
|
||||
// Skip adding the role to the user if they already have it.
|
||||
if ($account !== FALSE && !$account->hasRole($rid)) {
|
||||
// For efficiency manually save the original account before applying
|
||||
// any changes.
|
||||
$account->setOriginal(clone $account);
|
||||
$account->addRole($rid)->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
45
web/core/modules/user/src/Plugin/Action/BlockUser.php
Normal file
45
web/core/modules/user/src/Plugin/Action/BlockUser.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Blocks a user.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'user_block_user_action',
|
||||
label: new TranslatableMarkup('Block the selected users'),
|
||||
type: 'user'
|
||||
)]
|
||||
class BlockUser extends ActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($account = NULL) {
|
||||
// Skip blocking user if they are already blocked.
|
||||
if ($account !== FALSE && $account->isActive()) {
|
||||
// For efficiency manually save the original account before applying any
|
||||
// changes.
|
||||
$account->setOriginal(clone $account);
|
||||
$account->block();
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\user\UserInterface $object */
|
||||
$access = $object->status->access('edit', $account, TRUE)
|
||||
->andIf($object->access('update', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
94
web/core/modules/user/src/Plugin/Action/CancelUser.php
Normal file
94
web/core/modules/user/src/Plugin/Action/CancelUser.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Cancels a user account.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'user_cancel_user_action',
|
||||
label: new TranslatableMarkup('Cancel the selected user accounts'),
|
||||
type: 'user',
|
||||
confirm_form_route_name: 'user.multiple_cancel_confirm'
|
||||
)]
|
||||
class CancelUser extends ActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The tempstore factory.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a CancelUser object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->tempStoreFactory = $temp_store_factory;
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
$this->tempStoreFactory->get('user_user_operations_cancel')->set($this->currentUser->id(), $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$this->executeMultiple([$object]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\user\UserInterface $object */
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
105
web/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
Normal file
105
web/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base class for operations to change a user's role.
|
||||
*/
|
||||
abstract class ChangeUserRoleBase extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
use DependencyTrait;
|
||||
|
||||
/**
|
||||
* The user role entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeInterface $entity_type) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityType = $entity_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getDefinition('user_role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'rid' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$roles = Role::loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
unset($roles[RoleInterface::AUTHENTICATED_ID]);
|
||||
$roles = array_map(fn(RoleInterface $role) => $role->label(), $roles);
|
||||
$form['rid'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Role'),
|
||||
'#options' => $roles,
|
||||
'#default_value' => $this->configuration['rid'],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['rid'] = $form_state->getValue('rid');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
if (!empty($this->configuration['rid'])) {
|
||||
$prefix = $this->entityType->getConfigPrefix() . '.';
|
||||
$this->addDependency('config', $prefix . $this->configuration['rid']);
|
||||
}
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\user\UserInterface $object */
|
||||
$access = $object->access('update', $account, TRUE)
|
||||
->andIf($object->roles->access('edit', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
32
web/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
Normal file
32
web/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Removes a role from a user.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'user_remove_role_action',
|
||||
label: new TranslatableMarkup('Remove a role from the selected users'),
|
||||
type: 'user'
|
||||
)]
|
||||
class RemoveRoleUser extends ChangeUserRoleBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($account = NULL) {
|
||||
$rid = $this->configuration['rid'];
|
||||
// Skip removing the role from the user if they already don't have it.
|
||||
if ($account !== FALSE && $account->hasRole($rid)) {
|
||||
// For efficiency manually save the original account before applying
|
||||
// any changes.
|
||||
$account->setOriginal(clone $account);
|
||||
$account->removeRole($rid)->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
42
web/core/modules/user/src/Plugin/Action/UnblockUser.php
Normal file
42
web/core/modules/user/src/Plugin/Action/UnblockUser.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Unblocks a user.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'user_unblock_user_action',
|
||||
label: new TranslatableMarkup('Unblock the selected users'),
|
||||
type: 'user'
|
||||
)]
|
||||
class UnblockUser extends ActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($account = NULL) {
|
||||
// Skip unblocking user if they are already unblocked.
|
||||
if ($account !== FALSE && $account->isBlocked()) {
|
||||
$account->activate();
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\user\UserInterface $object */
|
||||
$access = $object->status->access('edit', $account, TRUE)
|
||||
->andIf($object->access('update', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
179
web/core/modules/user/src/Plugin/Block/UserLoginBlock.php
Normal file
179
web/core/modules/user/src/Plugin/Block/UserLoginBlock.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Block;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\Attribute\Block;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Security\TrustedCallbackInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationTrait;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\user\Form\UserLoginForm;
|
||||
use Drupal\user\UserInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'User login' block.
|
||||
*/
|
||||
#[Block(
|
||||
id: "user_login_block",
|
||||
admin_label: new TranslatableMarkup("User login"),
|
||||
category: new TranslatableMarkup("Forms")
|
||||
)]
|
||||
class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface {
|
||||
|
||||
use RedirectDestinationTrait;
|
||||
|
||||
/**
|
||||
* The route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new UserLoginBlock instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration, i.e. an array with configuration values keyed
|
||||
* by configuration option name. The special key 'context' may be used to
|
||||
* initialize the defined contexts by setting it to an array of context
|
||||
* values keyed by context names.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Drupal\Core\Form\FormBuilderInterface $formBuilder
|
||||
* The form builder.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
RouteMatchInterface $route_match,
|
||||
protected FormBuilderInterface $formBuilder,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('current_route_match'),
|
||||
$container->get('form_builder')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
$route_name = $this->routeMatch->getRouteName();
|
||||
if ($account->isAnonymous() && !in_array($route_name, ['user.login', 'user.logout'])) {
|
||||
return AccessResult::allowed()
|
||||
->addCacheContexts(['route.name', 'user.roles:anonymous']);
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$form = $this->formBuilder->getForm(UserLoginForm::class);
|
||||
unset($form['name']['#attributes']['autofocus']);
|
||||
$form['name']['#size'] = 15;
|
||||
$form['pass']['#size'] = 15;
|
||||
|
||||
// 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 placeholder 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.
|
||||
// cspell:disable-next-line
|
||||
$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') != UserInterface::REGISTER_ADMINISTRATORS_ONLY) {
|
||||
$items['create_account'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Create new account'),
|
||||
'#url' => Url::fromRoute('user.register', [], [
|
||||
'attributes' => [
|
||||
'title' => $this->t('Create a new user account.'),
|
||||
'class' => ['create-account-link'],
|
||||
],
|
||||
]),
|
||||
];
|
||||
}
|
||||
$items['request_password'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Reset your password'),
|
||||
'#url' => Url::fromRoute('user.pass', [], [
|
||||
'attributes' => [
|
||||
'title' => $this->t('Send password reset instructions via email.'),
|
||||
'class' => ['request-password-link'],
|
||||
],
|
||||
]),
|
||||
];
|
||||
return [
|
||||
'user_login_form' => $form,
|
||||
'user_links' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: Renders a form action URL including destination.
|
||||
*
|
||||
* This function is assigned as a #lazy_builder callback.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the form action.
|
||||
*
|
||||
* @see \Drupal\Core\Form\FormBuilder::renderPlaceholderFormAction()
|
||||
*/
|
||||
public static function renderPlaceholderFormAction() {
|
||||
return [
|
||||
'#type' => 'markup',
|
||||
'#markup' => UrlHelper::filterBadProtocol(Url::fromRoute('<current>', [], ['query' => \Drupal::destination()->getAsArray(), 'external' => FALSE])->toString()),
|
||||
'#cache' => ['contexts' => ['url.path', 'url.query_args']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function trustedCallbacks() {
|
||||
return ['renderPlaceholderFormAction'];
|
||||
}
|
||||
|
||||
}
|
||||
105
web/core/modules/user/src/Plugin/Condition/UserRole.php
Normal file
105
web/core/modules/user/src/Plugin/Condition/UserRole.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Condition;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Condition\Attribute\Condition;
|
||||
use Drupal\Core\Condition\ConditionPluginBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'User Role' condition.
|
||||
*/
|
||||
#[Condition(
|
||||
id: "user_role",
|
||||
label: new TranslatableMarkup("User Role"),
|
||||
context_definitions: [
|
||||
"user" => new EntityContextDefinition(
|
||||
data_type: "entity:user",
|
||||
label: new TranslatableMarkup("User"),
|
||||
),
|
||||
],
|
||||
)]
|
||||
class UserRole extends ConditionPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('When the user has the following roles'),
|
||||
'#default_value' => $this->configuration['roles'],
|
||||
'#options' => array_map(fn(RoleInterface $role) => Html::escape($role->label()), Role::loadMultiple()),
|
||||
'#description' => $this->t('If you select no roles, the condition will evaluate to TRUE for all users.'),
|
||||
];
|
||||
return parent::buildConfigurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'roles' => [],
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['roles'] = array_filter($form_state->getValue('roles'));
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summary() {
|
||||
// Use the role labels. They will be sanitized below.
|
||||
$roles = array_map(fn(RoleInterface $role) => $role->label(), Role::loadMultiple());
|
||||
$roles = array_intersect_key($roles, $this->configuration['roles']);
|
||||
if (count($roles) > 1) {
|
||||
$roles = implode(', ', $roles);
|
||||
}
|
||||
else {
|
||||
$roles = reset($roles);
|
||||
}
|
||||
if (!empty($this->configuration['negate'])) {
|
||||
return $this->t('The user is not a member of @roles', ['@roles' => $roles]);
|
||||
}
|
||||
else {
|
||||
return $this->t('The user is a member of @roles', ['@roles' => $roles]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evaluate() {
|
||||
if (empty($this->configuration['roles']) && !$this->isNegated()) {
|
||||
return TRUE;
|
||||
}
|
||||
$user = $this->getContextValue('user');
|
||||
return (bool) array_intersect($this->configuration['roles'], $user->getRoles());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
// Optimize cache context, if a user cache context is provided, only use
|
||||
// user.roles, since that's the only part this condition cares about.
|
||||
$contexts = [];
|
||||
foreach (parent::getCacheContexts() as $context) {
|
||||
$contexts[] = $context == 'user' ? 'user.roles' : $context;
|
||||
}
|
||||
return $contexts;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides local task definitions for all entity bundles.
|
||||
*/
|
||||
class UserLocalTask extends DeriverBase implements ContainerDeriverInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a UserLocalTask object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The translation manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('string_translation')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$this->derivatives = [];
|
||||
|
||||
$entity_definitions = $this->entityTypeManager->getDefinitions();
|
||||
foreach ($entity_definitions as $bundle_type_id => $bundle_entity_type) {
|
||||
if (!$bundle_entity_type->hasLinkTemplate('entity-permissions-form')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entity_type_id = $bundle_entity_type->getBundleOf()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity_type = $entity_definitions[$entity_type_id];
|
||||
if (!$base_route = $entity_type->get('field_ui_base_route')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->derivatives["permissions_$bundle_type_id"] = [
|
||||
'route_name' => "entity.$bundle_type_id.entity_permissions_form",
|
||||
'weight' => 10,
|
||||
'title' => $this->t('Manage permissions'),
|
||||
'base_route' => $base_route,
|
||||
] + $base_plugin_definition;
|
||||
|
||||
}
|
||||
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides specific access control for the user entity type.
|
||||
*/
|
||||
#[EntityReferenceSelection(
|
||||
id: "default:user",
|
||||
label: new TranslatableMarkup("User selection"),
|
||||
entity_types: ["user"],
|
||||
group: "default",
|
||||
weight: 1
|
||||
)]
|
||||
class UserSelection extends DefaultSelection {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Constructs a new UserSelection object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The entity type bundle info service.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Connection $connection, ?EntityFieldManagerInterface $entity_field_manager = NULL, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, ?EntityRepositoryInterface $entity_repository = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $module_handler, $current_user, $entity_field_manager, $entity_type_bundle_info, $entity_repository);
|
||||
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('current_user'),
|
||||
$container->get('database'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('entity.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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' => $configuration['include_anonymous'],
|
||||
];
|
||||
|
||||
// Add user specific filter options.
|
||||
$form['filter']['type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Filter by'),
|
||||
'#options' => [
|
||||
'_none' => $this->t('- None -'),
|
||||
'role' => $this->t('User role'),
|
||||
],
|
||||
// Use a form process callback to build #ajax property properly and also
|
||||
// to avoid code duplication.
|
||||
// @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcess()
|
||||
'#ajax' => TRUE,
|
||||
'#limit_validation_errors' => [],
|
||||
'#default_value' => $configuration['filter']['type'],
|
||||
];
|
||||
|
||||
$form['filter']['settings'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['class' => ['entity_reference-settings']],
|
||||
'#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']],
|
||||
];
|
||||
|
||||
if ($configuration['filter']['type'] == 'role') {
|
||||
$roles = Role::loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
unset($roles[RoleInterface::AUTHENTICATED_ID]);
|
||||
$roles = array_map(fn(RoleInterface $role) => $role->label(), $roles);
|
||||
$form['filter']['settings']['role'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Restrict to the selected roles'),
|
||||
'#required' => TRUE,
|
||||
'#options' => $roles,
|
||||
'#default_value' => $configuration['filter']['role'],
|
||||
];
|
||||
}
|
||||
|
||||
$form += parent::buildConfigurationForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
// Filter out the Anonymous user if the selection handler is configured to
|
||||
// exclude it.
|
||||
if (!$configuration['include_anonymous']) {
|
||||
$query->condition('uid', 0, '<>');
|
||||
}
|
||||
|
||||
// The user entity doesn't have a label column.
|
||||
if (isset($match)) {
|
||||
$query->condition('name', $match, $match_operator);
|
||||
}
|
||||
|
||||
// Filter by role.
|
||||
if (!empty($configuration['filter']['role'])) {
|
||||
$query->condition('roles', $configuration['filter']['role'], 'IN');
|
||||
}
|
||||
|
||||
// Adding the permission check is sadly insufficient for users: core
|
||||
// requires us to also know about the concept of 'blocked' and 'active'.
|
||||
if (!$this->currentUser->hasPermission('administer users')) {
|
||||
$query->condition('status', 1);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable user, it needs to be active.
|
||||
if (!$this->currentUser->hasPermission('administer users')) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user->activate();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
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(), $role));
|
||||
});
|
||||
}
|
||||
if (!$this->currentUser->hasPermission('administer users')) {
|
||||
$entities = array_filter($entities, function ($user) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
return $user->isActive();
|
||||
});
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityQueryAlter(SelectInterface $query) {
|
||||
parent::entityQueryAlter($query);
|
||||
|
||||
// Bail out early if we do not need to match the Anonymous user.
|
||||
if (!$this->getConfiguration()['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
|
||||
// database.
|
||||
$conditions = &$query->conditions();
|
||||
foreach ($conditions as $key => $condition) {
|
||||
if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
|
||||
// Remove the condition.
|
||||
unset($conditions[$key]);
|
||||
|
||||
// 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 = $this->connection->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 = $this->connection->condition('AND');
|
||||
$value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
|
||||
$value_part->compile($this->connection, $query);
|
||||
$or->condition(($this->connection->condition('AND'))
|
||||
->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
|
||||
->condition('base_table.uid', 0)
|
||||
);
|
||||
$query->condition($or);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'author' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'author',
|
||||
label: new TranslatableMarkup('Author'),
|
||||
description: new TranslatableMarkup('Display the referenced author user entity.'),
|
||||
field_types: [
|
||||
'entity_reference',
|
||||
],
|
||||
)]
|
||||
class AuthorFormatter extends EntityReferenceFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
|
||||
$elements[$delta] = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $entity,
|
||||
'#link_options' => ['attributes' => ['rel' => 'author']],
|
||||
'#cache' => [
|
||||
'tags' => $entity->getCacheTags(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return $field_definition->getFieldStorageDefinition()->getSetting('target_type') == 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity) {
|
||||
return $entity->access('view label', NULL, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'user_name' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'user_name',
|
||||
label: new TranslatableMarkup('User name'),
|
||||
description: new TranslatableMarkup('Display the user or author name.'),
|
||||
field_types: [
|
||||
'string',
|
||||
],
|
||||
)]
|
||||
class UserNameFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
$options = parent::defaultSettings();
|
||||
|
||||
$options['link_to_entity'] = TRUE;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::settingsForm($form, $form_state);
|
||||
|
||||
$form['link_to_entity'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Link to the user'),
|
||||
'#default_value' => $this->getSetting('link_to_entity'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
if ($user = $item->getEntity()) {
|
||||
if ($this->getSetting('link_to_entity')) {
|
||||
$elements[$delta] = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $user,
|
||||
'#link_options' => ['attributes' => ['rel' => 'user']],
|
||||
'#cache' => [
|
||||
'tags' => $user->getCacheTags(),
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$elements[$delta] = [
|
||||
'#markup' => $user->getDisplayName(),
|
||||
'#cache' => [
|
||||
'tags' => $user->getCacheTags(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return $field_definition->getTargetEntityTypeId() === 'user' && $field_definition->getName() === 'name';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\LanguageNegotiation;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\language\Attribute\LanguageNegotiation;
|
||||
use Drupal\language\LanguageNegotiationMethodBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class for identifying language from the user preferences.
|
||||
*/
|
||||
#[LanguageNegotiation(
|
||||
id: LanguageNegotiationUser::METHOD_ID,
|
||||
name: new TranslatableMarkup('User'),
|
||||
weight: -4,
|
||||
description: new TranslatableMarkup("Follow the user's language preference.")
|
||||
)]
|
||||
class LanguageNegotiationUser extends LanguageNegotiationMethodBase {
|
||||
|
||||
/**
|
||||
* The language negotiation method id.
|
||||
*/
|
||||
const METHOD_ID = 'language-user';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode(?Request $request = NULL) {
|
||||
$langcode = NULL;
|
||||
|
||||
// User preference (only for authenticated users).
|
||||
if ($this->languageManager && $this->currentUser->isAuthenticated()) {
|
||||
$preferred_langcode = $this->currentUser->getPreferredLangcode(FALSE);
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
if (!empty($preferred_langcode) && isset($languages[$preferred_langcode])) {
|
||||
$langcode = $preferred_langcode;
|
||||
}
|
||||
}
|
||||
|
||||
// No language preference from the user.
|
||||
return $langcode;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\LanguageNegotiation;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\PathProcessor\PathProcessorManager;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\AdminContext;
|
||||
use Drupal\Core\Routing\StackedRouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\language\Attribute\LanguageNegotiation;
|
||||
use Drupal\language\LanguageNegotiationMethodBase;
|
||||
use Drupal\Core\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\Routing\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
||||
|
||||
/**
|
||||
* Identifies admin language from the user preferences.
|
||||
*/
|
||||
#[LanguageNegotiation(
|
||||
id: LanguageNegotiationUserAdmin::METHOD_ID,
|
||||
name: new TranslatableMarkup('Account administration pages'),
|
||||
types: [LanguageInterface::TYPE_INTERFACE],
|
||||
weight: -10,
|
||||
description: new TranslatableMarkup('Account administration pages language setting.')
|
||||
)]
|
||||
class LanguageNegotiationUserAdmin extends LanguageNegotiationMethodBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The language negotiation method id.
|
||||
*/
|
||||
const METHOD_ID = 'language-user-admin';
|
||||
|
||||
/**
|
||||
* The admin context.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\AdminContext
|
||||
*/
|
||||
protected $adminContext;
|
||||
|
||||
/**
|
||||
* The router.
|
||||
*
|
||||
* This is only used when called from an event subscriber, before the request
|
||||
* has been populated with the route info.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* The path processor manager.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\PathProcessorManager
|
||||
*/
|
||||
protected $pathProcessorManager;
|
||||
|
||||
/**
|
||||
* The stacked route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\StackedRouteMatchInterface
|
||||
*/
|
||||
protected $stackedRouteMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new LanguageNegotiationUserAdmin instance.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\AdminContext $admin_context
|
||||
* The admin context.
|
||||
* @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $router
|
||||
* The router.
|
||||
* @param \Drupal\Core\PathProcessor\PathProcessorManager $path_processor_manager
|
||||
* The path processor manager.
|
||||
* @param \Drupal\Core\Routing\StackedRouteMatchInterface $stacked_route_match
|
||||
* The stacked route match.
|
||||
*/
|
||||
public function __construct(AdminContext $admin_context, UrlMatcherInterface $router, PathProcessorManager $path_processor_manager, StackedRouteMatchInterface $stacked_route_match) {
|
||||
$this->adminContext = $admin_context;
|
||||
$this->router = $router;
|
||||
$this->pathProcessorManager = $path_processor_manager;
|
||||
$this->stackedRouteMatch = $stacked_route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('router.admin_context'),
|
||||
$container->get('router'),
|
||||
$container->get('path_processor_manager'),
|
||||
$container->get('current_route_match')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode(?Request $request = NULL) {
|
||||
$langcode = NULL;
|
||||
|
||||
// User preference (only for administrators).
|
||||
if (($this->currentUser->hasPermission('access administration pages') || $this->currentUser->hasPermission('view the administration theme')) && ($preferred_admin_langcode = $this->currentUser->getPreferredAdminLangcode(FALSE)) && $this->isAdminPath($request)) {
|
||||
$langcode = $preferred_admin_langcode;
|
||||
}
|
||||
|
||||
// Not an admin, no admin language preference or not on an admin path.
|
||||
return $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given path is an administrative one.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the path is administrative, FALSE otherwise.
|
||||
*/
|
||||
protected function isAdminPath(Request $request) {
|
||||
$result = FALSE;
|
||||
if ($request && $this->adminContext) {
|
||||
// If called from an event subscriber, the request may not have the route
|
||||
// object yet (it is still being built), so use the router to look up
|
||||
// based on the path.
|
||||
$route_match = $this->stackedRouteMatch->getRouteMatchFromRequest($request);
|
||||
if ($route_match && !$route_object = $route_match->getRouteObject()) {
|
||||
try {
|
||||
// Some inbound path processors make changes to the request. Make a
|
||||
// copy as we're not actually routing the request so we do not want to
|
||||
// make changes.
|
||||
$cloned_request = clone $request;
|
||||
// Process the path as an inbound path. This will remove any language
|
||||
// prefixes and other path components that inbound processing would
|
||||
// clear out, so we can attempt to load the route clearly.
|
||||
$path = $this->pathProcessorManager->processInbound(urldecode(rtrim($cloned_request->getPathInfo(), '/')), $cloned_request);
|
||||
$attributes = $this->router->match($path);
|
||||
}
|
||||
catch (ExceptionInterface | HttpException) {
|
||||
return FALSE;
|
||||
}
|
||||
$route_object = $attributes[RouteObjectInterface::ROUTE_OBJECT];
|
||||
}
|
||||
$result = $this->adminContext->isAdminRoute($route_object);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Menu;
|
||||
|
||||
use Drupal\Core\Menu\MenuLinkDefault;
|
||||
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A menu link that shows "Log in" or "Log out" as appropriate.
|
||||
*/
|
||||
class LoginLogoutMenuLink extends MenuLinkDefault {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new LoginLogoutMenuLink.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
|
||||
* The static override storage.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override, AccountInterface $current_user) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $static_override);
|
||||
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('menu_link.static.overrides'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
if ($this->currentUser->isAuthenticated()) {
|
||||
return $this->t('Log out');
|
||||
}
|
||||
else {
|
||||
return $this->t('Log in');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
if ($this->currentUser->isAuthenticated()) {
|
||||
return 'user.logout';
|
||||
}
|
||||
else {
|
||||
return 'user.login';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['user.roles:authenticated'];
|
||||
}
|
||||
|
||||
}
|
||||
180
web/core/modules/user/src/Plugin/Search/UserSearch.php
Normal file
180
web/core/modules/user/src/Plugin/Search/UserSearch.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Search;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\PagerSelectExtender;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\search\Attribute\Search;
|
||||
use Drupal\search\Plugin\SearchPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Executes a keyword search for users against the {users} database table.
|
||||
*/
|
||||
#[Search(
|
||||
id: 'user_search',
|
||||
title: new TranslatableMarkup('Users'),
|
||||
)]
|
||||
class UserSearch extends SearchPluginBase implements AccessibleInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('database'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('current_user'),
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UserSearch object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
*/
|
||||
public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, array $configuration, $plugin_id, $plugin_definition) {
|
||||
$this->database = $database;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->currentUser = $current_user;
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->addCacheTags(['user_list']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation = 'view', ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowedIf(!empty($account) && $account->hasPermission('access user profiles'))->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute() {
|
||||
$results = [];
|
||||
if (!$this->isSearchExecutable()) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Process the keywords.
|
||||
$keys = $this->keywords;
|
||||
// Escape for LIKE matching.
|
||||
$keys = $this->database->escapeLike($keys);
|
||||
// Replace wildcards with MySQL/PostgreSQL wildcards.
|
||||
$keys = preg_replace('!\*+!', '%', $keys);
|
||||
|
||||
// Run the query to find matching users.
|
||||
$query = $this->database
|
||||
->select('users_field_data', 'users')
|
||||
->extend(PagerSelectExtender::class);
|
||||
$query->fields('users', ['uid']);
|
||||
$query->condition('default_langcode', 1);
|
||||
if ($this->currentUser->hasPermission('administer users')) {
|
||||
// Administrators can also search in the otherwise private email field,
|
||||
// and they don't need to be restricted to only active users.
|
||||
$query->fields('users', ['mail']);
|
||||
$query->condition($query->orConditionGroup()
|
||||
->condition('name', '%' . $keys . '%', 'LIKE')
|
||||
->condition('mail', '%' . $keys . '%', 'LIKE')
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Regular users can only search via usernames, and we do not show them
|
||||
// blocked accounts.
|
||||
$query->condition('name', '%' . $keys . '%', 'LIKE')
|
||||
->condition('status', 1);
|
||||
}
|
||||
$uids = $query
|
||||
->limit(15)
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$accounts = $this->entityTypeManager->getStorage('user')->loadMultiple($uids);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$result = [
|
||||
'title' => $account->getDisplayName(),
|
||||
'link' => $account->toUrl('canonical', ['absolute' => TRUE])->toString(),
|
||||
];
|
||||
if ($this->currentUser->hasPermission('administer users')) {
|
||||
$result['title'] .= ' (' . $account->getEmail() . ')';
|
||||
}
|
||||
$this->addCacheableDependency($account);
|
||||
$results[] = $result;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $help;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
|
||||
|
||||
/**
|
||||
* Checks if the plain text password is provided for editing a protected field.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'ProtectedUserField',
|
||||
label: new TranslatableMarkup('Password required for protected field change', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class ProtectedUserFieldConstraint extends SymfonyConstraint {
|
||||
|
||||
/**
|
||||
* Violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = "Your current password is missing or incorrect; it's required to change the %name.";
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the ProtectedUserFieldConstraint constraint.
|
||||
*/
|
||||
class ProtectedUserFieldConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* User storage handler.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage handler.
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(UserStorageInterface $user_storage, AccountProxyInterface $current_user) {
|
||||
$this->userStorage = $user_storage;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('user'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($items, Constraint $constraint): void {
|
||||
if (!isset($items)) {
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\Core\Field\FieldItemListInterface $items */
|
||||
$field = $items->getFieldDefinition();
|
||||
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $items->getEntity();
|
||||
if (!isset($account) || !empty($account->_skipProtectedUserFieldConstraint)) {
|
||||
// Looks like we are validating a field not being part of a user, or the
|
||||
// constraint should be skipped, so do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Only validate for existing entities and if this is the current user.
|
||||
if (!$account->isNew() && $account->id() == $this->currentUser->id()) {
|
||||
|
||||
/** @var \Drupal\user\UserInterface $account_unchanged */
|
||||
$account_unchanged = $this->userStorage
|
||||
->loadUnchanged($account->id());
|
||||
|
||||
$changed = FALSE;
|
||||
|
||||
// Special case for the password, it being empty means that the existing
|
||||
// password should not be changed, ignore empty password fields.
|
||||
$value = $items->value;
|
||||
if ($field->getName() != 'pass' || !empty($value)) {
|
||||
// Compare the values of the field this is being validated on.
|
||||
$changed = $items->getValue() != $account_unchanged->get($field->getName())->getValue();
|
||||
}
|
||||
if ($changed && (!$account->checkExistingPassword($account_unchanged))) {
|
||||
$this->context->addViolation($constraint->message, ['%name' => $field->getLabel()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
|
||||
|
||||
/**
|
||||
* Checks if a role exists.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'RoleExists',
|
||||
label: new TranslatableMarkup('Role exists', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class RoleExistsConstraint extends SymfonyConstraint {
|
||||
|
||||
/**
|
||||
* The error message if validation fails.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = "The role with id '@rid' does not exist.";
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Validates that a role exists.
|
||||
*/
|
||||
class RoleExistsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* Create a new RoleExistsConstraintValidator instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(private readonly EntityTypeManagerInterface $entity_type_manager) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): static {
|
||||
return new static(
|
||||
$container->get(EntityTypeManagerInterface::class),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint): void {
|
||||
assert($constraint instanceof RoleExistsConstraint);
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new UnexpectedTypeException($value, 'string');
|
||||
}
|
||||
|
||||
$roleStorage = $this->entity_type_manager->getStorage('user_role');
|
||||
if (!$roleStorage->load($value)) {
|
||||
$this->context->addViolation($constraint->message, [
|
||||
'@rid' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraints\Choice;
|
||||
|
||||
/**
|
||||
* Constraint for the user cancel method.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'UserCancelMethod',
|
||||
label: new TranslatableMarkup('UserCancelMethod', [], ['context' => 'Validation']),
|
||||
)]
|
||||
class UserCancelMethodsConstraint implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): Choice {
|
||||
$configuration['choices'] = array_keys(user_cancel_methods()['#options']);
|
||||
return new Choice($configuration);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
|
||||
|
||||
/**
|
||||
* Checks if the user's email address is provided if required.
|
||||
*
|
||||
* The user mail field is NOT required if account originally had no mail set
|
||||
* and the user performing the edit has 'administer users' permission.
|
||||
* This allows users without email address to be edited and deleted.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'UserMailRequired',
|
||||
label: new TranslatableMarkup('User email required', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class UserMailRequired extends SymfonyConstraint {
|
||||
|
||||
/**
|
||||
* Violation message. Use the same message as FormValidator.
|
||||
*
|
||||
* Note that the name argument is not sanitized so that translators only have
|
||||
* one string to translate. The name is sanitized in self::validate().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = '@name field is required.';
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Checks if the user's email address is provided if required.
|
||||
*
|
||||
* The user mail field is NOT required if account originally had no mail set
|
||||
* and the user performing the edit has 'administer users' permission.
|
||||
* This allows users without email address to be edited and deleted.
|
||||
*/
|
||||
class UserMailRequiredValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($items, Constraint $constraint): void {
|
||||
/** @var \Drupal\Core\Field\FieldItemListInterface $items */
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $items->getEntity();
|
||||
if (!isset($account)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing_value = NULL;
|
||||
|
||||
// Only validate for existing user.
|
||||
if (!$account->isNew()) {
|
||||
$account_unchanged = \Drupal::entityTypeManager()
|
||||
->getStorage('user')
|
||||
->loadUnchanged($account->id());
|
||||
$existing_value = $account_unchanged->getEmail();
|
||||
}
|
||||
|
||||
$required = !(!$existing_value && \Drupal::currentUser()->hasPermission('administer users'));
|
||||
|
||||
if ($required && (!isset($items) || $items->isEmpty())) {
|
||||
$this->context->addViolation($constraint->message, ['@name' => $account->getFieldDefinition('mail')->getLabel()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
|
||||
|
||||
/**
|
||||
* Checks if a user's email address is unique on the site.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'UserMailUnique',
|
||||
label: new TranslatableMarkup('User email unique', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class UserMailUnique extends UniqueFieldConstraint {
|
||||
|
||||
/**
|
||||
* The default violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'The email address %value is already taken.';
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
|
||||
|
||||
/**
|
||||
* Checks if a value is a valid user name.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'UserName',
|
||||
label: new TranslatableMarkup('User name', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class UserNameConstraint extends SymfonyConstraint {
|
||||
|
||||
/**
|
||||
* The violation message when there is no username.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $emptyMessage = 'You must enter a username.';
|
||||
|
||||
/**
|
||||
* The violation message when the username begins with whitespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $spaceBeginMessage = 'The username cannot begin with a space.';
|
||||
|
||||
/**
|
||||
* The violation message when the username ends with whitespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $spaceEndMessage = 'The username cannot end with a space.';
|
||||
|
||||
/**
|
||||
* The violation message when the username has consecutive whitespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $multipleSpacesMessage = 'The username cannot contain multiple spaces in a row.';
|
||||
|
||||
/**
|
||||
* The violation message when the username uses an invalid character.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $illegalMessage = 'The username contains an illegal character.';
|
||||
|
||||
/**
|
||||
* The violation message when the username length exceeds the maximum allowed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tooLongMessage = 'The username %name is too long: it must be %max characters or less.';
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the UserName constraint.
|
||||
*/
|
||||
class UserNameConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($items, Constraint $constraint): void {
|
||||
if (empty($items) || ($items instanceof FieldItemListInterface && $items->isEmpty())) {
|
||||
$this->context->addViolation($constraint->emptyMessage);
|
||||
return;
|
||||
}
|
||||
$name = $items instanceof FieldItemListInterface ? $items->first()->value : $items;
|
||||
if (str_starts_with($name, ' ')) {
|
||||
$this->context->addViolation($constraint->spaceBeginMessage);
|
||||
}
|
||||
if (str_ends_with($name, ' ')) {
|
||||
$this->context->addViolation($constraint->spaceEndMessage);
|
||||
}
|
||||
if (str_contains($name, ' ')) {
|
||||
$this->context->addViolation($constraint->multipleSpacesMessage);
|
||||
}
|
||||
if (preg_match('/[^\x{80}-\x{F7} a-z0-9@+_.\'-]/i', $name)
|
||||
|| preg_match(
|
||||
// Non-printable ISO-8859-1 + NBSP
|
||||
'/[\x{80}-\x{A0}' .
|
||||
// Soft-hyphen
|
||||
'\x{AD}' .
|
||||
// Various space characters
|
||||
'\x{2000}-\x{200F}' .
|
||||
// Bidirectional text overrides
|
||||
'\x{2028}-\x{202F}' .
|
||||
// Various text hinting characters
|
||||
'\x{205F}-\x{206F}' .
|
||||
// Byte order mark
|
||||
'\x{FEFF}' .
|
||||
// Full-width latin
|
||||
'\x{FF01}-\x{FF60}' .
|
||||
// Replacement characters
|
||||
'\x{FFF9}-\x{FFFD}' .
|
||||
// NULL byte and control characters
|
||||
'\x{0}-\x{1F}]/u',
|
||||
$name)
|
||||
) {
|
||||
$this->context->addViolation($constraint->illegalMessage);
|
||||
}
|
||||
if (mb_strlen($name) > UserInterface::USERNAME_MAX_LENGTH) {
|
||||
$this->context->addViolation($constraint->tooLongMessage, ['%name' => $name, '%max' => UserInterface::USERNAME_MAX_LENGTH]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
|
||||
|
||||
/**
|
||||
* Checks if a user name is unique on the site.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'UserNameUnique',
|
||||
label: new TranslatableMarkup('User name unique', [], ['context' => 'Validation'])
|
||||
)]
|
||||
class UserNameUnique extends UniqueFieldConstraint {
|
||||
|
||||
/**
|
||||
* The default violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'The username %value is already taken.';
|
||||
|
||||
}
|
||||
67
web/core/modules/user/src/Plugin/migrate/ProfileValues.php
Normal file
67
web/core/modules/user/src/Plugin/migrate/ProfileValues.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate;
|
||||
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\Plugin\Migration;
|
||||
|
||||
/**
|
||||
* Plugin class for user migrations dealing with profile values.
|
||||
*/
|
||||
class ProfileValues extends Migration {
|
||||
|
||||
/**
|
||||
* Flag determining whether the process plugin has been initialized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $init = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcess() {
|
||||
if (!$this->init) {
|
||||
$this->init = TRUE;
|
||||
$definition['source'] = [
|
||||
'plugin' => 'profile_field',
|
||||
'ignore_map' => TRUE,
|
||||
] + $this->source;
|
||||
$definition['destination']['plugin'] = 'null';
|
||||
$definition['idMap']['plugin'] = 'null';
|
||||
try {
|
||||
$this->checkRequirements();
|
||||
$profile_field_migration = $this->migrationPluginManager->createStubMigration($definition);
|
||||
$migrate_executable = new MigrateExecutable($profile_field_migration);
|
||||
$source_plugin = $profile_field_migration->getSourcePlugin();
|
||||
$source_plugin->checkRequirements();
|
||||
foreach ($source_plugin as $row) {
|
||||
$name = $row->getSourceProperty('name');
|
||||
$fid = $row->getSourceProperty('fid');
|
||||
// The user profile field name can be greater than 32 characters. Use
|
||||
// the migrated profile field name in the process pipeline.
|
||||
$configuration =
|
||||
[
|
||||
'migration' => 'user_profile_field',
|
||||
'source_ids' => $fid,
|
||||
'no_stub' => TRUE,
|
||||
];
|
||||
$plugin = $this->processPluginManager->createInstance('migration_lookup', $configuration, $profile_field_migration);
|
||||
$new_value = $plugin->transform($fid, $migrate_executable, $row, 'tmp');
|
||||
if (isset($new_value[1])) {
|
||||
// Set the destination to the migrated profile field name.
|
||||
$this->process[$new_value[1]] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RequirementsException) {
|
||||
// The checkRequirements() call will fail when the profile module does
|
||||
// not exist on the source site, or if the required migrations have not
|
||||
// yet run.
|
||||
}
|
||||
}
|
||||
return parent::getProcess();
|
||||
}
|
||||
|
||||
}
|
||||
50
web/core/modules/user/src/Plugin/migrate/User.php
Normal file
50
web/core/modules/user/src/Plugin/migrate/User.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate;
|
||||
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\FieldMigration;
|
||||
|
||||
/**
|
||||
* Plugin class for Drupal 7 user migrations dealing with fields and profiles.
|
||||
*/
|
||||
class User extends FieldMigration {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcess() {
|
||||
if (!$this->init) {
|
||||
$this->init = TRUE;
|
||||
$this->fieldDiscovery->addEntityFieldProcesses($this, 'user');
|
||||
|
||||
$definition = [
|
||||
'source' => [
|
||||
'plugin' => 'profile_field',
|
||||
'ignore_map' => TRUE,
|
||||
],
|
||||
'idMap' => [
|
||||
'plugin' => 'null',
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'null',
|
||||
],
|
||||
];
|
||||
try {
|
||||
$profile_migration = $this->migrationPluginManager->createStubMigration($definition);
|
||||
// Ensure that Profile is enabled in the source DB.
|
||||
$profile_migration->checkRequirements();
|
||||
foreach ($profile_migration->getSourcePlugin() as $row) {
|
||||
$name = $row->getSourceProperty('name');
|
||||
$this->process[$name] = $name;
|
||||
}
|
||||
}
|
||||
catch (RequirementsException) {
|
||||
// The checkRequirements() call will fail when the profile module does
|
||||
// not exist on the source site.
|
||||
}
|
||||
}
|
||||
return parent::getProcess();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\EmailItem;
|
||||
use Drupal\Core\Password\PasswordInterface;
|
||||
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\user\UserNameItem;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a destination plugin for migrating user entities.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The example below migrates users and preserves original passwords from a
|
||||
* source that has passwords as MD5 hashes without salt. The passwords will be
|
||||
* salted and re-hashed before they are saved to the destination Drupal
|
||||
* database. The MD5 hash used in the example is a hash of 'password'.
|
||||
*
|
||||
* The example uses the EmbeddedDataSource source plugin for the sake of
|
||||
* simplicity. The mapping between old user_ids and new Drupal uids is saved in
|
||||
* the migration map table.
|
||||
* @code
|
||||
* id: custom_user_migration
|
||||
* label: Custom user migration
|
||||
* source:
|
||||
* plugin: embedded_data
|
||||
* data_rows:
|
||||
* -
|
||||
* user_id: 1
|
||||
* name: JohnSmith
|
||||
* mail: johnsmith@example.com
|
||||
* hash: '5f4dcc3b5aa765d61d8327deb882cf99'
|
||||
* ids:
|
||||
* user_id:
|
||||
* type: integer
|
||||
* process:
|
||||
* name: name
|
||||
* mail: mail
|
||||
* pass: hash
|
||||
* status:
|
||||
* plugin: default_value
|
||||
* default_value: 1
|
||||
* destination:
|
||||
* plugin: entity:user
|
||||
* md5_passwords: true
|
||||
* @endcode
|
||||
*
|
||||
* For configuration options inherited from the parent class, refer to
|
||||
* \Drupal\migrate\Plugin\migrate\destination\EntityContentBase.
|
||||
*
|
||||
* The example above is about migrating an MD5 password hash. For more examples
|
||||
* on different password hash types and a list of other user properties, refer
|
||||
* to the handbook documentation:
|
||||
* @see https://www.drupal.org/docs/8/api/migrate-api/migrate-destination-plugins-examples/migrating-users
|
||||
*/
|
||||
#[MigrateDestination('entity:user')]
|
||||
class EntityUser extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* The password service class.
|
||||
*
|
||||
* @var \Drupal\Core\Password\PasswordInterface
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* Builds a user entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager service.
|
||||
* @param \Drupal\Core\Password\PasswordInterface $password
|
||||
* The password service.
|
||||
* @param \Drupal\Core\Session\AccountSwitcherInterface|null $account_switcher
|
||||
* The account switcher service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface|null $entity_type_bundle_info
|
||||
* The entity type bundle info service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, PasswordInterface $password, ?AccountSwitcherInterface $account_switcher = NULL, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_field_manager, $field_type_manager, $account_switcher, $entity_type_bundle_info);
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type)),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('plugin.manager.field.field_type'),
|
||||
$container->get('password'),
|
||||
$container->get('account_switcher'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
// Do not overwrite the root account password.
|
||||
if ($row->getDestinationProperty('uid') == 1) {
|
||||
$row->removeDestinationProperty('pass');
|
||||
}
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
// Do not overwrite the root account password.
|
||||
if ($entity->id() != 1) {
|
||||
// Set the pre_hashed password so that the PasswordItem field does not
|
||||
// hash already hashed passwords. If the md5_passwords configuration
|
||||
// option is set we need to rehash the password and prefix with a U.
|
||||
// @see \Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem::preSave()
|
||||
$entity->pass->pre_hashed = TRUE;
|
||||
if (isset($this->configuration['md5_passwords'])) {
|
||||
$entity->pass->value = 'U' . $this->password->hash($entity->pass->value);
|
||||
}
|
||||
}
|
||||
return parent::save($entity, $old_destination_id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
parent::processStubRow($row);
|
||||
|
||||
$field_definitions = $this->entityFieldManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(),
|
||||
$this->getKey('bundle'));
|
||||
|
||||
// Name is generated using a dedicated sample value generator to ensure
|
||||
// uniqueness and a valid length.
|
||||
// @todo Remove this as part of https://www.drupal.org/node/3352288.
|
||||
$name = UserNameItem::generateSampleValue($field_definitions['name']);
|
||||
$row->setDestinationProperty('name', reset($name));
|
||||
|
||||
// Email address is not defined as required in the base field definition but
|
||||
// is effectively required by the UserMailRequired constraint. This means
|
||||
// that Entity::processStubRow() did not populate it - we do it here.
|
||||
$mail = EmailItem::generateSampleValue($field_definitions['mail']);
|
||||
$row->setDestinationProperty('mail', reset($mail));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighestId() {
|
||||
$highest_id = parent::getHighestId();
|
||||
|
||||
// Every Drupal site must have a user with UID of 1 and it's normal for
|
||||
// migrations to overwrite this user.
|
||||
if ($highest_id === 1) {
|
||||
return 0;
|
||||
}
|
||||
return $highest_id;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityConfigBase;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a destination plugin for migrating user role entities.
|
||||
*/
|
||||
#[MigrateDestination('entity:user_role')]
|
||||
class EntityUserRole extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* All permissions on the destination site.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $destinationPermissions = [];
|
||||
|
||||
/**
|
||||
* Builds a user role entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param array $destination_permissions
|
||||
* All available permissions.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, array $destination_permissions) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $language_manager, $config_factory);
|
||||
$this->destinationPermissions = $destination_permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
$entity_type_id = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory'),
|
||||
array_keys($container->get('user.permissions')->getPermissions()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []): array|bool {
|
||||
$permissions = $row->getDestinationProperty('permissions') ?? [];
|
||||
|
||||
// Get permissions that do not exist on the destination.
|
||||
$invalid_permissions = array_diff($permissions, $this->destinationPermissions);
|
||||
if ($invalid_permissions) {
|
||||
sort($invalid_permissions);
|
||||
// Log the message in the migration message table.
|
||||
$message = "Permission(s) '" . implode("', '", $invalid_permissions) . "' not found.";
|
||||
$this->migration->getIdMap()
|
||||
->saveMessage($row->getSourceIdValues(), $message, MigrationInterface::MESSAGE_WARNING);
|
||||
}
|
||||
|
||||
$valid_permissions = array_intersect($permissions, $this->destinationPermissions);
|
||||
$row->setDestinationProperty('permissions', $valid_permissions);
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\user\UserData as UserDataStorage;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\Plugin\migrate\destination\DestinationBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
|
||||
/**
|
||||
* Migration destination for user data.
|
||||
*/
|
||||
#[MigrateDestination('user_data')]
|
||||
class UserData extends DestinationBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserData
|
||||
*/
|
||||
protected $userData;
|
||||
|
||||
/**
|
||||
* Builds a user data entity destination.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\user\UserData $user_data
|
||||
* The user data service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, UserDataStorage $user_data) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->userData = $user_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('user.data')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$uid = $row->getDestinationProperty('uid');
|
||||
$module = $row->getDestinationProperty('module');
|
||||
$key = $row->getDestinationProperty('key');
|
||||
$this->userData->set($module, $uid, $key, $row->getDestinationProperty('settings'));
|
||||
|
||||
return [$uid, $module, $key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['uid']['type'] = 'integer';
|
||||
$ids['module']['type'] = 'string';
|
||||
$ids['key']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'uid' => 'The user id.',
|
||||
'module' => 'The module name responsible for the settings.',
|
||||
'key' => 'The setting key to save under.',
|
||||
'settings' => 'The settings to save.',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Plugin to replace !tokens with [tokens].
|
||||
*/
|
||||
#[MigrateProcess(
|
||||
id: "convert_tokens",
|
||||
handle_multiples: TRUE,
|
||||
)]
|
||||
class ConvertTokens extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$tokens = [
|
||||
'!site' => '[site:name]',
|
||||
'!username' => '[user:name]',
|
||||
'!mailto' => '[user:mail]',
|
||||
'!login_uri' => '[site:login-url]',
|
||||
'!uri_brief' => '[site:url-brief]',
|
||||
'!edit_uri' => '[user:edit-url]',
|
||||
'!login_url' => '[user:one-time-login-url]',
|
||||
'!uri' => '[site:url]',
|
||||
'!date' => '[date:medium]',
|
||||
'!password' => '',
|
||||
];
|
||||
|
||||
// Given that our source is a database column that could hold a NULL
|
||||
// value, sometimes that filters down to here. str_replace() cannot
|
||||
// handle NULLs as the subject, so we reset to an empty string.
|
||||
if (is_null($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return str_replace(array_keys($tokens), $tokens, $value);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException('Value must be a string.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Determines the settings for the profile field.
|
||||
*/
|
||||
#[MigrateProcess('profile_field_settings')]
|
||||
class ProfileFieldSettings extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($type, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$settings = [];
|
||||
switch ($type) {
|
||||
case 'date':
|
||||
$settings['datetime_type'] = 'date';
|
||||
break;
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Language\LanguageManager;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a process plugin for the user langcode.
|
||||
*/
|
||||
#[MigrateProcess('user_langcode')]
|
||||
class UserLangcode extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManager
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a UserLangcode object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* Plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\Language\LanguageManager $language_manager
|
||||
* The language manager service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManager $language_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('language_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!isset($this->configuration['fallback_to_site_default'])) {
|
||||
$this->configuration['fallback_to_site_default'] = TRUE;
|
||||
}
|
||||
|
||||
// If the user's language is empty, it means the locale module was not
|
||||
// installed, so the user's langcode should be English and the user's
|
||||
// preferred_langcode and preferred_admin_langcode should fallback to the
|
||||
// default language.
|
||||
if (empty($value)) {
|
||||
if ($this->configuration['fallback_to_site_default']) {
|
||||
return $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
else {
|
||||
return 'en';
|
||||
}
|
||||
}
|
||||
// If the user's language does not exist, use the default language.
|
||||
elseif ($this->languageManager->getLanguage($value) === NULL) {
|
||||
return $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
|
||||
// If the langcode is a valid one, just return it.
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Keep the predefined roles for rid 1 and 2.
|
||||
*/
|
||||
#[MigrateProcess('user_update_8002')]
|
||||
class UserUpdate8002 extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Keep the predefined roles for rid 1 and 2.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$rid = $row->getSourceProperty('rid');
|
||||
$map = [
|
||||
1 => 'anonymous',
|
||||
2 => 'authenticated',
|
||||
];
|
||||
return $map[$rid] ?? $value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Component\Utility\FilterArray;
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Determines the settings property and translation.
|
||||
*/
|
||||
#[MigrateProcess(
|
||||
id: "d6_profile_field_option_translation",
|
||||
handle_multiples: TRUE,
|
||||
)]
|
||||
class ProfileFieldOptionTranslation extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
[$field_type, $translation] = $value;
|
||||
|
||||
$new_value = NULL;
|
||||
if (isset($translation)) {
|
||||
$allowed_values = [];
|
||||
$list = explode("\n", $translation);
|
||||
$list = array_map('trim', $list);
|
||||
$list = FilterArray::removeEmptyStrings($list);
|
||||
if ($field_type === 'list_string') {
|
||||
foreach ($list as $value) {
|
||||
$allowed_values[] = ['label' => $value];
|
||||
}
|
||||
}
|
||||
$new_value = ['settings.allowed_values', $allowed_values];
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Core\Datetime\TimeZoneFormHelper;
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Converts user time zones from time zone offsets to time zone names.
|
||||
*/
|
||||
#[MigrateProcess('user_update_7002')]
|
||||
class UserUpdate7002 extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* System timezones.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $timezones;
|
||||
|
||||
/**
|
||||
* Contains the system.theme configuration object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $dateConfig;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, Config $date_config) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->dateConfig = $date_config;
|
||||
if (!isset(static::$timezones)) {
|
||||
static::$timezones = TimeZoneFormHelper::getOptionsList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('config.factory')->get('system.date')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$timezone = NULL;
|
||||
|
||||
if ($row->hasSourceProperty('timezone_name')) {
|
||||
if (isset(static::$timezones[$row->getSourceProperty('timezone_name')])) {
|
||||
$timezone = $row->getSourceProperty('timezone_name');
|
||||
}
|
||||
}
|
||||
if (!$timezone && $row->hasSourceProperty('event_timezone')) {
|
||||
if (isset(static::$timezones[$row->getSourceProperty('event_timezone')])) {
|
||||
$timezone = $row->getSourceProperty('event_timezone');
|
||||
}
|
||||
}
|
||||
|
||||
if ($timezone === NULL) {
|
||||
$timezone = $this->dateConfig->get('timezone.default');
|
||||
}
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
}
|
||||
133
web/core/modules/user/src/Plugin/migrate/source/ProfileField.php
Normal file
133
web/core/modules/user/src/Plugin/migrate/source/ProfileField.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Drupal 6/7 profile field source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "profile_field",
|
||||
* source_module = "profile"
|
||||
* )
|
||||
*/
|
||||
class ProfileField extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* The source table containing profile field info.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldTable;
|
||||
|
||||
/**
|
||||
* The source table containing the profile values.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $valueTable;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$this->setTableNames();
|
||||
return $this->select($this->fieldTable, 'pf')->fields('pf');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
if ($row->getSourceProperty('type') == 'selection') {
|
||||
// Get the current options.
|
||||
$current_options = preg_split("/[\r\n]+/", $row->getSourceProperty('options'));
|
||||
// Select the list values from the profile_values table to ensure we get
|
||||
// them all since they can get out of sync with profile_fields.
|
||||
$options = $this->select($this->valueTable, 'pv')
|
||||
->distinct()
|
||||
->fields('pv', ['value'])
|
||||
->condition('fid', $row->getSourceProperty('fid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$options = array_merge($current_options, $options);
|
||||
// array_combine() takes care of any duplicates options.
|
||||
$row->setSourceProperty('options', array_combine($options, $options));
|
||||
}
|
||||
|
||||
if ($row->getSourceProperty('type') == 'checkbox') {
|
||||
// D6 profile checkboxes values are always 0 or 1 (with no labels), so we
|
||||
// need to create two label-less options that will get 0 and 1 for their
|
||||
// keys.
|
||||
$row->setSourceProperty('options', [NULL, NULL]);
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'fid' => $this->t('Primary Key: Unique profile field ID.'),
|
||||
'title' => $this->t('Title of the field shown to the end user.'),
|
||||
'name' => $this->t('Internal name of the field used in the form HTML and URLs.'),
|
||||
'explanation' => $this->t('Explanation of the field to end users.'),
|
||||
'category' => $this->t('Profile category that the field will be grouped under.'),
|
||||
'page' => $this->t("Title of page used for browsing by the field's value"),
|
||||
'type' => $this->t('Type of form field.'),
|
||||
'weight' => $this->t('Weight of field in relation to other profile fields.'),
|
||||
'required' => $this->t('Whether the user is required to enter a value. (0 = no, 1 = yes)'),
|
||||
'register' => $this->t('Whether the field is visible in the user registration form. (1 = yes, 0 = no)'),
|
||||
'visibility' => $this->t('The level of visibility for the field. (0 = hidden, 1 = private, 2 = public on profile but not member list pages, 3 = public on profile and list pages)'),
|
||||
'autocomplete' => $this->t('Whether form auto-completion is enabled. (0 = disabled, 1 = enabled)'),
|
||||
'options' => $this->t('List of options to be used in a list selection field.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['fid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
$this->setTableNames();
|
||||
if (!$this->getDatabase()->schema()->tableExists($this->fieldTable)) {
|
||||
// If we make it to here, the profile module isn't installed.
|
||||
throw new RequirementsException('Profile module not enabled on source site');
|
||||
}
|
||||
parent::checkRequirements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set the profile field table names.
|
||||
*/
|
||||
protected function setTableNames() {
|
||||
if (empty($this->fieldTable) || empty($this->valueTable)) {
|
||||
if ($this->getModuleSchemaVersion('system') >= 7000) {
|
||||
$this->fieldTable = 'profile_field';
|
||||
$this->valueTable = 'profile_value';
|
||||
}
|
||||
else {
|
||||
$this->fieldTable = 'profile_fields';
|
||||
$this->valueTable = 'profile_values';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
|
||||
|
||||
/**
|
||||
* Drupal 6/7 user picture field instance source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @todo Support default picture?
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "user_picture_instance",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPictureInstance extends DrupalSqlBase {
|
||||
|
||||
use DummyQueryTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initializeIterator() {
|
||||
return new \ArrayIterator([
|
||||
[
|
||||
'id' => '',
|
||||
'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'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'file_directory' => 'The directory to store images..',
|
||||
'max_filesize' => 'The maximum allowed file size in KBs.',
|
||||
'max_resolution' => "The maximum dimensions.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['id']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\user\Plugin\migrate\source\ProfileField;
|
||||
|
||||
// cspell:ignore nprofile objectid
|
||||
|
||||
/**
|
||||
* Drupal 6 i18n profile field option labels source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_profile_field_option_translation",
|
||||
* source_module = "i18nprofile"
|
||||
* )
|
||||
*/
|
||||
class ProfileFieldOptionTranslation extends ProfileField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = parent::query();
|
||||
$query
|
||||
->fields('i18n', ['property', 'objectid'])
|
||||
->fields('lt', ['translation', 'language'])
|
||||
->condition('i18n.type', 'field')
|
||||
->condition('property', 'options');
|
||||
$query->leftJoin('i18n_strings', 'i18n', '[pf].[name] = [i18n].[objectid]');
|
||||
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return parent::fields() +
|
||||
[
|
||||
'property' => $this->t('Option ID.'),
|
||||
'objectid' => $this->t('Field name'),
|
||||
'language' => $this->t('Language for this field.'),
|
||||
'translation' => $this->t('Translation of either the title or explanation.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return parent::getIds() +
|
||||
[
|
||||
'language' => ['type' => 'string'],
|
||||
'property' => ['type' => 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 profile fields values source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_profile_field_values",
|
||||
* source_module = "profile"
|
||||
* )
|
||||
*/
|
||||
class ProfileFieldValues extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('profile_values', 'pv')
|
||||
->distinct()
|
||||
->fields('pv', ['uid']);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Find profile values for this row.
|
||||
$query = $this->select('profile_values', 'pv')
|
||||
->fields('pv', ['fid', 'value']);
|
||||
$query->leftJoin('profile_fields', 'pf', '[pf].[fid] = [pv].[fid]');
|
||||
$query->fields('pf', ['name', 'type']);
|
||||
$query->condition('uid', $row->getSourceProperty('uid'));
|
||||
$results = $query->execute();
|
||||
|
||||
foreach ($results as $profile_value) {
|
||||
// Check special case for date. We need to unserialize.
|
||||
if ($profile_value['type'] == 'date') {
|
||||
$date = unserialize($profile_value['value']);
|
||||
$date = date('Y-m-d', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
|
||||
$row->setSourceProperty($profile_value['name'], ['value' => $date]);
|
||||
}
|
||||
elseif ($profile_value['type'] == 'list') {
|
||||
// Explode by newline and comma.
|
||||
$row->setSourceProperty($profile_value['name'], preg_split("/[\r\n,]+/", $profile_value['value']));
|
||||
}
|
||||
else {
|
||||
$row->setSourceProperty($profile_value['name'], [$profile_value['value']]);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'fid' => $this->t('Unique profile field ID.'),
|
||||
'uid' => $this->t('The user Id.'),
|
||||
'value' => $this->t('The value for this field.'),
|
||||
];
|
||||
|
||||
$query = $this->select('profile_values', 'pv')
|
||||
->fields('pv', ['fid', 'value']);
|
||||
$query->leftJoin('profile_fields', 'pf', '[pf].[fid] = [pv].[fid]');
|
||||
$query->fields('pf', ['name', 'title']);
|
||||
$results = $query->execute();
|
||||
foreach ($results as $profile) {
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$fields[$profile['name']] = $this->t($profile['title']);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'uid' => [
|
||||
'type' => 'integer',
|
||||
'alias' => 'pv',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
101
web/core/modules/user/src/Plugin/migrate/source/d6/Role.php
Normal file
101
web/core/modules/user/src/Plugin/migrate/source/d6/Role.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 role source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_role",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class Role extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* List of filter IDs per role IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filterPermissions = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('role', 'r')
|
||||
->fields('r', ['rid', 'name'])
|
||||
->orderBy('r.rid');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'rid' => $this->t('Role ID.'),
|
||||
'name' => $this->t('The name of the user role.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$filter_roles = $this->select('filter_formats', 'f')
|
||||
->fields('f', ['format', 'roles'])
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
foreach ($filter_roles as $format => $roles) {
|
||||
// Drupal 6 code: $roles = ','. implode(',', $roles) .',';
|
||||
// Remove the beginning and ending comma.
|
||||
foreach (explode(',', trim($roles, ',')) as $rid) {
|
||||
$this->filterPermissions[$rid][] = $format;
|
||||
}
|
||||
}
|
||||
return parent::initializeIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$rid = $row->getSourceProperty('rid');
|
||||
$permissions = $this->select('permission', 'p')
|
||||
->fields('p', ['perm'])
|
||||
->condition('rid', $rid)
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
// If a role has no permissions then set to an empty array. The role will
|
||||
// be migrated and given the default D8 permissions.
|
||||
if ($permissions) {
|
||||
$row->setSourceProperty('permissions', explode(', ', $permissions));
|
||||
}
|
||||
else {
|
||||
$row->setSourceProperty('permissions', []);
|
||||
}
|
||||
if (isset($this->filterPermissions[$rid])) {
|
||||
$row->setSourceProperty("filter_permissions:$rid", $this->filterPermissions[$rid]);
|
||||
}
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['rid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
137
web/core/modules/user/src/Plugin/migrate/source/d6/User.php
Normal file
137
web/core/modules/user/src/Plugin/migrate/source/d6/User.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 user source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class User extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('users', 'u')
|
||||
->fields('u', array_keys($this->baseFields()))
|
||||
->condition('u.uid', 0, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = $this->baseFields();
|
||||
|
||||
// Add roles field.
|
||||
$fields['roles'] = $this->t('Roles');
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// User roles.
|
||||
$roles = $this->select('users_roles', 'ur')
|
||||
->fields('ur', ['rid'])
|
||||
->condition('ur.uid', $row->getSourceProperty('uid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('roles', $roles);
|
||||
|
||||
// We are adding here the Event contributed module column.
|
||||
// @see https://api.drupal.org/api/drupal/modules%21user%21user.install/function/user_update_7002/7
|
||||
if ($row->hasSourceProperty('timezone_id') && $row->getSourceProperty('timezone_id')) {
|
||||
if ($this->getDatabase()->schema()->tableExists('event_timezones')) {
|
||||
$event_timezone = $this->select('event_timezones', 'e')
|
||||
->fields('e', ['name'])
|
||||
->condition('e.timezone', $row->getSourceProperty('timezone_id'))
|
||||
->execute()
|
||||
->fetchField();
|
||||
if ($event_timezone) {
|
||||
$row->setSourceProperty('event_timezone', $event_timezone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unserialize Data.
|
||||
$data = $row->getSourceProperty('data');
|
||||
if ($data !== NULL) {
|
||||
$row->setSourceProperty('data', unserialize($row->getSourceProperty('data')));
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'uid' => [
|
||||
'type' => 'integer',
|
||||
'alias' => 'u',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user base fields to be migrated.
|
||||
*
|
||||
* @return array
|
||||
* Associative array having field name as key and description as value.
|
||||
*/
|
||||
protected function baseFields() {
|
||||
$fields = [
|
||||
'uid' => $this->t('User ID'),
|
||||
'name' => $this->t('Username'),
|
||||
'pass' => $this->t('Password'),
|
||||
'mail' => $this->t('Email address'),
|
||||
'theme' => $this->t('Theme'),
|
||||
'signature' => $this->t('Signature'),
|
||||
'signature_format' => $this->t('Signature format'),
|
||||
'created' => $this->t('Registered timestamp'),
|
||||
'access' => $this->t('Last access timestamp'),
|
||||
'login' => $this->t('Last login timestamp'),
|
||||
'status' => $this->t('Status'),
|
||||
'timezone' => $this->t('Timezone'),
|
||||
'language' => $this->t('Language'),
|
||||
'picture' => $this->t('Picture'),
|
||||
'init' => $this->t('Init'),
|
||||
'data' => $this->t('User data'),
|
||||
];
|
||||
|
||||
// The database connection may not exist, for example, when building
|
||||
// the Migrate Message form.
|
||||
if ($source_database = $this->database) {
|
||||
// Possible field added by Date contributed module.
|
||||
// @see https://api.drupal.org/api/drupal/modules%21user%21user.install/function/user_update_7002/7
|
||||
if ($source_database->schema()->fieldExists('users', 'timezone_name')) {
|
||||
$fields['timezone_name'] = $this->t('Timezone (Date)');
|
||||
}
|
||||
|
||||
// Possible field added by Event contributed module.
|
||||
// @see https://api.drupal.org/api/drupal/modules%21user%21user.install/function/user_update_7002/7
|
||||
if ($source_database->schema()->fieldExists('users', 'timezone_id')) {
|
||||
$fields['timezone_id'] = $this->t('Timezone (Event)');
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 user picture source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @todo Support default picture?
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_picture",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPicture extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('users', 'u')
|
||||
->condition('picture', '', '<>')
|
||||
->fields('u', ['uid', 'access', 'picture'])
|
||||
->orderBy('u.access');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'uid' => 'Primary Key: Unique user ID.',
|
||||
'access' => 'Timestamp for previous time user accessed the site.',
|
||||
'picture' => "Path to the user's uploaded picture.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['uid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Drupal 6 user picture source from database.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - site_path: (optional) The path to the site directory relative to Drupal
|
||||
* root. Defaults to 'sites/default'.
|
||||
*
|
||||
* For additional configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_user_picture_file",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class UserPictureFile extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* The file directory path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filePath;
|
||||
|
||||
/**
|
||||
* The temporary file path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tempFilePath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('users', 'u')
|
||||
->condition('u.picture', '', '<>')
|
||||
->fields('u', ['uid', 'picture']);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initializeIterator() {
|
||||
$site_path = $this->configuration['site_path'] ?? 'sites/default';
|
||||
$this->filePath = $this->variableGet('file_directory_path', $site_path . '/files') . '/';
|
||||
$this->tempFilePath = $this->variableGet('file_directory_temp', '/tmp') . '/';
|
||||
return parent::initializeIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$row->setSourceProperty('filename', basename($row->getSourceProperty('picture')));
|
||||
$row->setSourceProperty('file_directory_path', $this->filePath);
|
||||
$row->setSourceProperty('temp_directory_path', $this->tempFilePath);
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'picture' => "Path to the user's uploaded picture.",
|
||||
'filename' => 'The picture filename.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['uid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
63
web/core/modules/user/src/Plugin/migrate/source/d7/Role.php
Normal file
63
web/core/modules/user/src/Plugin/migrate/source/d7/Role.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 7 role source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_user_role",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class Role extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('role', 'r')->fields('r');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'rid' => $this->t('Role ID.'),
|
||||
'name' => $this->t('The name of the user role.'),
|
||||
'weight' => $this->t('The weight of the role.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$permissions = $this->select('role_permission', 'rp')
|
||||
->fields('rp', ['permission'])
|
||||
->condition('rid', $row->getSourceProperty('rid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('permissions', $permissions);
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['rid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
139
web/core/modules/user/src/Plugin/migrate/source/d7/User.php
Normal file
139
web/core/modules/user/src/Plugin/migrate/source/d7/User.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
|
||||
/**
|
||||
* Drupal 7 user source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_user",
|
||||
* source_module = "user"
|
||||
* )
|
||||
*/
|
||||
class User extends FieldableEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('users', 'u')
|
||||
->fields('u')
|
||||
->condition('u.uid', 0, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'uid' => $this->t('User ID'),
|
||||
'name' => $this->t('Username'),
|
||||
'pass' => $this->t('Password'),
|
||||
'mail' => $this->t('Email address'),
|
||||
'signature' => $this->t('Signature'),
|
||||
'signature_format' => $this->t('Signature format'),
|
||||
'created' => $this->t('Registered timestamp'),
|
||||
'access' => $this->t('Last access timestamp'),
|
||||
'login' => $this->t('Last login timestamp'),
|
||||
'status' => $this->t('Status'),
|
||||
'timezone' => $this->t('Timezone'),
|
||||
'language' => $this->t('Language'),
|
||||
'picture' => $this->t('Picture'),
|
||||
'init' => $this->t('Init'),
|
||||
'data' => $this->t('User data'),
|
||||
'roles' => $this->t('Roles'),
|
||||
];
|
||||
|
||||
// Profile fields.
|
||||
if ($this->moduleExists('profile')) {
|
||||
$fields += $this->select('profile_fields', 'pf')
|
||||
->fields('pf', ['name', 'title'])
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$uid = $row->getSourceProperty('uid');
|
||||
|
||||
$roles = $this->select('users_roles', 'ur')
|
||||
->fields('ur', ['rid'])
|
||||
->condition('ur.uid', $uid)
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('roles', $roles);
|
||||
|
||||
$row->setSourceProperty('data', unserialize($row->getSourceProperty('data') ?? ''));
|
||||
|
||||
// If this entity was translated using Entity Translation, we need to get
|
||||
// its source language to get the field values in the right language.
|
||||
// The translations will be migrated by the d7_user_entity_translation
|
||||
// migration.
|
||||
$entity_translatable = $this->isEntityTranslatable('user');
|
||||
$source_language = $this->getEntityTranslationSourceLanguage('user', $uid);
|
||||
$language = $entity_translatable && $source_language ? $source_language : $row->getSourceProperty('language');
|
||||
$row->setSourceProperty('entity_language', $language);
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('user') as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity and the field are
|
||||
// translatable.
|
||||
$field_language = $entity_translatable && $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('user', $field_name, $uid, NULL, $field_language));
|
||||
}
|
||||
|
||||
// Get profile field values. This code is lifted directly from the D6
|
||||
// ProfileFieldValues plugin.
|
||||
if ($this->getDatabase()->schema()->tableExists('profile_value')) {
|
||||
$query = $this->select('profile_value', 'pv')
|
||||
->fields('pv', ['fid', 'value']);
|
||||
$query->leftJoin('profile_field', 'pf', '[pf].[fid] = [pv].[fid]');
|
||||
$query->fields('pf', ['name', 'type']);
|
||||
$query->condition('uid', $row->getSourceProperty('uid'));
|
||||
$results = $query->execute();
|
||||
|
||||
foreach ($results as $profile_value) {
|
||||
if ($profile_value['type'] == 'date') {
|
||||
$date = unserialize($profile_value['value']);
|
||||
$date = date('Y-m-d', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
|
||||
$row->setSourceProperty($profile_value['name'], ['value' => $date]);
|
||||
}
|
||||
elseif ($profile_value['type'] == 'list') {
|
||||
// Explode by newline and comma.
|
||||
$row->setSourceProperty($profile_value['name'], preg_split("/[\r\n,]+/", $profile_value['value']));
|
||||
}
|
||||
else {
|
||||
$row->setSourceProperty($profile_value['name'], [$profile_value['value']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'uid' => [
|
||||
'type' => 'integer',
|
||||
'alias' => 'u',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
|
||||
/**
|
||||
* Drupal 7 user entity translations source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_user_entity_translation",
|
||||
* source_module = "entity_translation"
|
||||
* )
|
||||
*/
|
||||
class UserEntityTranslation extends FieldableEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('entity_translation', 'et')
|
||||
->fields('et')
|
||||
->condition('et.entity_type', 'user')
|
||||
->condition('et.source', '', '<>');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$uid = $row->getSourceProperty('entity_id');
|
||||
$language = $row->getSourceProperty('language');
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('user') as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity is translatable.
|
||||
$field_language = $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('user', $field_name, $uid, NULL, $field_language));
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'entity_type' => $this->t('The entity type this translation relates to'),
|
||||
'entity_id' => $this->t('The entity id this translation relates to'),
|
||||
'revision_id' => $this->t('The entity revision id this translation relates to'),
|
||||
'language' => $this->t('The target language for this translation.'),
|
||||
'source' => $this->t('The source language from which this translation was created.'),
|
||||
'uid' => $this->t('The author of this translation.'),
|
||||
'status' => $this->t('Boolean indicating whether the translation is published (visible to non-administrators).'),
|
||||
'translate' => $this->t('A boolean indicating whether this translation needs to be updated.'),
|
||||
'created' => $this->t('The Unix timestamp when the translation was created.'),
|
||||
'changed' => $this->t('The Unix timestamp when the translation was most recently saved.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'entity_id' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'language' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\rest\resource;
|
||||
|
||||
use Drupal\Core\Config\ImmutableConfig;
|
||||
use Drupal\Core\Password\PasswordGeneratorInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\rest\Attribute\RestResource;
|
||||
use Drupal\rest\ModifiedResourceResponse;
|
||||
use Drupal\rest\Plugin\ResourceBase;
|
||||
use Drupal\rest\Plugin\rest\resource\EntityResourceAccessTrait;
|
||||
use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\UserInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
/**
|
||||
* Represents user registration as a resource.
|
||||
*/
|
||||
#[RestResource(
|
||||
id: "user_registration",
|
||||
label: new TranslatableMarkup("User registration"),
|
||||
serialization_class: User::class,
|
||||
uri_paths: [
|
||||
"create" => "/user/register",
|
||||
],
|
||||
)]
|
||||
class UserRegistrationResource extends ResourceBase {
|
||||
|
||||
use EntityResourceValidationTrait;
|
||||
use EntityResourceAccessTrait;
|
||||
|
||||
/**
|
||||
* Constructs a new UserRegistrationResource instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param array $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\Config\ImmutableConfig $userSettings
|
||||
* A user settings config instance.
|
||||
* @param \Drupal\Core\Session\AccountInterface $currentUser
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Password\PasswordGeneratorInterface $passwordGenerator
|
||||
* The password generator.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
array $serializer_formats,
|
||||
LoggerInterface $logger,
|
||||
protected ImmutableConfig $userSettings,
|
||||
protected AccountInterface $currentUser,
|
||||
protected PasswordGeneratorInterface $passwordGenerator,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->getParameter('serializer.formats'),
|
||||
$container->get('logger.factory')->get('rest'),
|
||||
$container->get('config.factory')->get('user.settings'),
|
||||
$container->get('current_user'),
|
||||
$container->get('password_generator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to user registration POST request.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* The user account entity.
|
||||
*
|
||||
* @return \Drupal\rest\ModifiedResourceResponse
|
||||
* The HTTP response object.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function post(?UserInterface $account = NULL) {
|
||||
$this->ensureAccountCanRegister($account);
|
||||
|
||||
// Only activate new users if visitors are allowed to register.
|
||||
if ($this->userSettings->get('register') == UserInterface::REGISTER_VISITORS) {
|
||||
$account->activate();
|
||||
}
|
||||
else {
|
||||
$account->block();
|
||||
}
|
||||
|
||||
// Generate password if email verification required.
|
||||
if ($this->userSettings->get('verify_mail')) {
|
||||
$account->setPassword($this->passwordGenerator->generate());
|
||||
}
|
||||
|
||||
$this->checkEditFieldAccess($account);
|
||||
|
||||
// Make sure that the user entity is valid (email and name are valid).
|
||||
$this->validate($account);
|
||||
|
||||
// Create the account.
|
||||
$account->save();
|
||||
|
||||
$this->sendEmailNotifications($account);
|
||||
|
||||
return new ModifiedResourceResponse($account, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the account can be registered in this request.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* The user account to register.
|
||||
*/
|
||||
protected function ensureAccountCanRegister(?UserInterface $account = NULL) {
|
||||
if ($account === NULL) {
|
||||
throw new BadRequestHttpException('No user account data for registration received.');
|
||||
}
|
||||
|
||||
// POSTed user accounts must not have an ID set, because we always want to
|
||||
// create new entities here.
|
||||
if (!$account->isNew()) {
|
||||
throw new BadRequestHttpException('An ID has been set and only new user accounts can be registered.');
|
||||
}
|
||||
|
||||
// Only allow anonymous users to register, authenticated users with the
|
||||
// necessary permissions can POST a new user to the "user" REST resource.
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource
|
||||
if (!$this->currentUser->isAnonymous()) {
|
||||
throw new AccessDeniedHttpException('Only anonymous users can register a user.');
|
||||
}
|
||||
|
||||
// Verify that the current user can register a user account.
|
||||
if ($this->userSettings->get('register') == UserInterface::REGISTER_ADMINISTRATORS_ONLY) {
|
||||
throw new AccessDeniedHttpException('You cannot register a new user account.');
|
||||
}
|
||||
|
||||
if (!$this->userSettings->get('verify_mail')) {
|
||||
if (empty($account->getPassword())) {
|
||||
// If no email verification then the user must provide a password.
|
||||
throw new UnprocessableEntityHttpException('No password provided.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!empty($account->getPassword())) {
|
||||
// If email verification required then a password cannot provided.
|
||||
// The password will be set when the user logs in.
|
||||
throw new UnprocessableEntityHttpException('A Password cannot be specified. It will be generated on login.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends email notifications if necessary for user that was registered.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $account
|
||||
* The user account.
|
||||
*/
|
||||
protected function sendEmailNotifications(UserInterface $account) {
|
||||
$approval_settings = $this->userSettings->get('register');
|
||||
// No email verification is required. Activating the user.
|
||||
if ($approval_settings == UserInterface::REGISTER_VISITORS) {
|
||||
// No administrator approval required.
|
||||
_user_mail_notify('register_no_approval_required', $account);
|
||||
}
|
||||
// Administrator approval required.
|
||||
elseif ($approval_settings == UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
|
||||
_user_mail_notify('register_pending_approval', $account);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
171
web/core/modules/user/src/Plugin/views/access/Permission.php
Normal file
171
web/core/modules/user/src/Plugin/views/access/Permission.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\access;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\user\PermissionHandlerInterface;
|
||||
use Drupal\views\Attribute\ViewsAccess;
|
||||
use Drupal\views\Plugin\views\access\AccessPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Access plugin that provides permission-based access control.
|
||||
*
|
||||
* @ingroup views_access_plugins
|
||||
*/
|
||||
#[ViewsAccess(
|
||||
id: 'perm',
|
||||
title: new TranslatableMarkup('Permission'),
|
||||
help: new TranslatableMarkup('Access will be granted to users with the specified permission string.'),
|
||||
)]
|
||||
class Permission extends AccessPluginBase implements CacheableDependencyInterface {
|
||||
use DeprecatedServicePropertyTrait;
|
||||
|
||||
/**
|
||||
* The service properties that should raise a deprecation error.
|
||||
*/
|
||||
private array $deprecatedProperties = ['moduleHandler' => 'module_handler'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $usesOptions = TRUE;
|
||||
|
||||
/**
|
||||
* The permission handler.
|
||||
*
|
||||
* @var \Drupal\user\PermissionHandlerInterface
|
||||
*/
|
||||
protected $permissionHandler;
|
||||
|
||||
/**
|
||||
* Module extension list.
|
||||
*/
|
||||
protected ModuleExtensionList $moduleExtensionList;
|
||||
|
||||
/**
|
||||
* Constructs a Permission object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
|
||||
* The permission handler.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList|\Drupal\Core\Extension\ModuleHandlerInterface $module_extension_list
|
||||
* The module extension list.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, PermissionHandlerInterface $permission_handler, ModuleExtensionList|ModuleHandlerInterface $module_extension_list) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->permissionHandler = $permission_handler;
|
||||
if ($module_extension_list instanceof ModuleHandlerInterface) {
|
||||
@trigger_error('Calling ' . __METHOD__ . '() with the $module_extension_list argument as ModuleHandlerInterface is deprecated in drupal:10.3.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3310017', E_USER_DEPRECATED);
|
||||
$module_extension_list = \Drupal::service('extension.list.module');
|
||||
}
|
||||
$this->moduleExtensionList = $module_extension_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('user.permissions'),
|
||||
$container->get('extension.list.module'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
return $account->hasPermission($this->options['perm']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterRouteDefinition(Route $route) {
|
||||
$route->setRequirement('_permission', $this->options['perm']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summaryTitle() {
|
||||
$permissions = $this->permissionHandler->getPermissions();
|
||||
if (isset($permissions[$this->options['perm']])) {
|
||||
return $permissions[$this->options['perm']]['title'];
|
||||
}
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
return $this->t($this->options['perm']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['perm'] = ['default' => 'access content'];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
// Get list of permissions
|
||||
$perms = [];
|
||||
$permissions = $this->permissionHandler->getPermissions();
|
||||
foreach ($permissions as $perm => $perm_item) {
|
||||
$provider = $perm_item['provider'];
|
||||
$display_name = $this->moduleExtensionList->getName($provider);
|
||||
$perms[$display_name][$perm] = strip_tags($perm_item['title']);
|
||||
}
|
||||
|
||||
$form['perm'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $perms,
|
||||
'#title' => $this->t('Permission'),
|
||||
'#default_value' => $this->options['perm'],
|
||||
'#description' => $this->t('Only users with the selected permission flag will be able to access this display.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['user.permissions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
178
web/core/modules/user/src/Plugin/views/access/Role.php
Normal file
178
web/core/modules/user/src/Plugin/views/access/Role.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\access;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Drupal\views\Attribute\ViewsAccess;
|
||||
use Drupal\views\Plugin\views\access\AccessPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Access plugin that provides role-based access control.
|
||||
*
|
||||
* @ingroup views_access_plugins
|
||||
*/
|
||||
#[ViewsAccess(
|
||||
id: 'role',
|
||||
title: new TranslatableMarkup('Role'),
|
||||
help: new TranslatableMarkup('Access will be granted to users with any of the specified roles.'),
|
||||
)]
|
||||
class Role extends AccessPluginBase implements CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $usesOptions = TRUE;
|
||||
|
||||
/**
|
||||
* The role storage.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorageInterface
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* Constructs a Role object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\user\RoleStorageInterface $role_storage
|
||||
* The role storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RoleStorageInterface $role_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->roleStorage = $role_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('user_role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
return !empty(array_intersect(array_filter($this->options['role']), $account->getRoles()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterRouteDefinition(Route $route) {
|
||||
if ($this->options['role']) {
|
||||
$route->setRequirement('_role', (string) implode('+', $this->options['role']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summaryTitle() {
|
||||
$count = count($this->options['role']);
|
||||
if ($count < 1) {
|
||||
return $this->t('No role(s) selected');
|
||||
}
|
||||
elseif ($count > 1) {
|
||||
return $this->t('Multiple roles');
|
||||
}
|
||||
else {
|
||||
$rid = reset($this->options['role']);
|
||||
return $this->roleStorage->load($rid)->label();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['role'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
$form['role'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Role'),
|
||||
'#default_value' => $this->options['role'],
|
||||
'#options' => array_map(fn(RoleInterface $role) => Html::escape($role->label()), $this->roleStorage->loadMultiple()),
|
||||
'#description' => $this->t('Only the checked roles will be able to access this display.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$role = $form_state->getValue(['access_options', 'role']);
|
||||
$role = array_filter($role);
|
||||
|
||||
if (!$role) {
|
||||
$form_state->setError($form['role'], $this->t('You must select at least one role if type is "by role"'));
|
||||
}
|
||||
|
||||
$form_state->setValue(['access_options', 'role'], $role);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
foreach (array_keys($this->options['role']) as $rid) {
|
||||
if ($role = $this->roleStorage->load($rid)) {
|
||||
$dependencies[$role->getConfigDependencyKey()][] = $role->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['user.roles'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
69
web/core/modules/user/src/Plugin/views/argument/RolesRid.php
Normal file
69
web/core/modules/user/src/Plugin/views/argument/RolesRid.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\views\Attribute\ViewsArgument;
|
||||
use Drupal\views\Plugin\views\argument\ManyToOne;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Allow role ID(s) as argument.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
#[ViewsArgument(
|
||||
id: 'user__roles_rid'
|
||||
)]
|
||||
class RolesRid extends ManyToOne {
|
||||
|
||||
/**
|
||||
* The role entity storage.
|
||||
*
|
||||
* @var \Drupal\user\RoleStorage
|
||||
*/
|
||||
protected $roleStorage;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\user\Plugin\views\argument\RolesRid object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->roleStorage = $entity_type_manager->getStorage('user_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function titleQuery() {
|
||||
$entities = $this->roleStorage->loadMultiple($this->value);
|
||||
$titles = [];
|
||||
foreach ($entities as $entity) {
|
||||
$titles[] = $entity->label();
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
||||
16
web/core/modules/user/src/Plugin/views/argument/Uid.php
Normal file
16
web/core/modules/user/src/Plugin/views/argument/Uid.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument;
|
||||
|
||||
use Drupal\views\Attribute\ViewsArgument;
|
||||
use Drupal\views\Plugin\views\argument\EntityArgument;
|
||||
|
||||
/**
|
||||
* Argument handler to accept a user id.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*/
|
||||
#[ViewsArgument(
|
||||
id: 'user_uid'
|
||||
)]
|
||||
class Uid extends EntityArgument {}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument_default;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\views\Attribute\ViewsArgumentDefault;
|
||||
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Default argument plugin to extract the current user.
|
||||
*
|
||||
* This plugin actually has no options so it does not need to do a great deal.
|
||||
*/
|
||||
#[ViewsArgumentDefault(
|
||||
id: 'current_user',
|
||||
title: new TranslatableMarkup('User ID from logged in user'),
|
||||
)]
|
||||
class CurrentUser extends ArgumentDefaultPluginBase implements CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* CurrentUser constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Session\AccountInterface|null $currentUser
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, protected ?AccountInterface $currentUser = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
if ($this->currentUser === NULL) {
|
||||
@trigger_error('Calling ' . __CLASS__ . '::__construct() without the $currentUser argument is deprecated in drupal:11.2.0 and is required in drupal:12.0.0. See https://www.drupal.org/node/3347878', E_USER_DEPRECATED);
|
||||
$this->currentUser = \Drupal::currentUser();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('current_user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgument() {
|
||||
return $this->currentUser->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['user'];
|
||||
}
|
||||
|
||||
}
|
||||
117
web/core/modules/user/src/Plugin/views/argument_default/User.php
Normal file
117
web/core/modules/user/src/Plugin/views/argument_default/User.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument_default;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\views\Attribute\ViewsArgumentDefault;
|
||||
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Default argument plugin to extract a user from request.
|
||||
*/
|
||||
#[ViewsArgumentDefault(
|
||||
id: 'user',
|
||||
title: new TranslatableMarkup('User ID from route context'),
|
||||
)]
|
||||
class User extends ArgumentDefaultPluginBase implements CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* The route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new User instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('current_route_match')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['user'] = ['default' => ''];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['user'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Also look for a node and use the node author'),
|
||||
'#default_value' => $this->options['user'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgument() {
|
||||
|
||||
// If there is a user object in the current route.
|
||||
if ($user = $this->routeMatch->getParameter('user')) {
|
||||
if ($user instanceof UserInterface) {
|
||||
return $user->id();
|
||||
}
|
||||
}
|
||||
|
||||
// If option to use node author; and node in current route.
|
||||
if (!empty($this->options['user']) && $node = $this->routeMatch->getParameter('node')) {
|
||||
if ($node instanceof NodeInterface) {
|
||||
return $node->getOwnerId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['url'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument_validator;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\views\argument_validator\Entity;
|
||||
|
||||
/**
|
||||
* Validate whether an argument is a valid user.
|
||||
*
|
||||
* This supports either numeric arguments (UID) or strings (username) and
|
||||
* converts either one into the user's UID. This validator also sets the
|
||||
* argument's title to the username.
|
||||
*/
|
||||
class User extends Entity {
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_type_bundle_info);
|
||||
|
||||
$this->userStorage = $entity_type_manager->getStorage('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['restrict_roles'] = ['default' => FALSE];
|
||||
$options['roles'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
$sanitized_id = ArgumentPluginBase::encodeValidatorId($this->definition['id']);
|
||||
|
||||
$form['restrict_roles'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Restrict user based on role'),
|
||||
'#default_value' => $this->options['restrict_roles'],
|
||||
];
|
||||
|
||||
$roles = Role::loadMultiple();
|
||||
unset($roles[RoleInterface::ANONYMOUS_ID]);
|
||||
$roles = array_map(fn(RoleInterface $role) => Html::escape($role->label()), $roles);
|
||||
$form['roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Restrict to the selected roles'),
|
||||
'#options' => $roles,
|
||||
'#default_value' => $this->options['roles'],
|
||||
'#description' => $this->t('If no roles are selected, users from any role will be allowed.'),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[validate][options][' . $sanitized_id . '][restrict_roles]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) {
|
||||
// Filter trash out of the options so we don't store giant unnecessary
|
||||
// arrays
|
||||
$options['roles'] = array_filter($options['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validateEntity(EntityInterface $entity) {
|
||||
/** @var \Drupal\user\UserInterface $entity */
|
||||
$role_check_success = TRUE;
|
||||
// See if we're filtering users based on roles.
|
||||
if (!empty($this->options['restrict_roles']) && !empty($this->options['roles'])) {
|
||||
$roles = $this->options['roles'];
|
||||
if (!(bool) array_intersect($entity->getRoles(), $roles)) {
|
||||
$role_check_success = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $role_check_success && parent::validateEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
foreach ($this->entityTypeManager->getStorage('user_role')->loadMultiple(array_keys($this->options['roles'])) as $role) {
|
||||
$dependencies[$role->getConfigDependencyKey()][] = $role->getConfigDependencyName();
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\user\Plugin\views\argument_validator;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\views\Attribute\ViewsArgumentValidator;
|
||||
|
||||
/**
|
||||
* Validates whether a user name is valid.
|
||||
*/
|
||||
#[ViewsArgumentValidator(
|
||||
id: 'user_name',
|
||||
title: new TranslatableMarkup('User name'),
|
||||
entity_type: 'user'
|
||||
)]
|
||||
class UserName extends User {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition('user');
|
||||
|
||||
$form['multiple']['#options'] = [
|
||||
0 => $this->t('Single name', ['%type' => $entity_type->getLabel()]),
|
||||
1 => $this->t('One or more names separated by , or +', ['%type' => $entity_type->getLabel()]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateArgument($argument) {
|
||||
if ($this->multipleCapable && $this->options['multiple']) {
|
||||
// At this point only interested in individual IDs no matter what type,
|
||||
// just splitting by the allowed delimiters.
|
||||
$names = array_filter(preg_split('/[,+ ]/', $argument));
|
||||
}
|
||||
elseif ($argument) {
|
||||
$names = [$argument];
|
||||
}
|
||||
// No specified argument should be invalid.
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$accounts = $this->userStorage->loadByProperties(['name' => $names]);
|
||||
|
||||
// If there are no accounts, return FALSE now. As we will not enter the
|
||||
// loop below otherwise.
|
||||
if (empty($accounts)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Validate each account. If any fails break out and return false.
|
||||
foreach ($accounts as $account) {
|
||||
if (!in_array($account->getAccountName(), $names) || !$this->validateEntity($account)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processSummaryArguments(&$args) {
|
||||
// If the validation says the input is a username, we should reverse the
|
||||
// argument so it works for example for generation summary URLs.
|
||||
$uids_arg_keys = array_flip($args);
|
||||
|
||||
foreach ($this->userStorage->loadMultiple($args) as $uid => $account) {
|
||||
$args[$uids_arg_keys[$uid]] = $account->label();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user