diff -u b/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php --- b/core/modules/user/src/AccountForm.php +++ b/core/modules/user/src/AccountForm.php @@ -94,11 +94,11 @@ '#type' => 'email', '#title' => $this->t('Email address'), '#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by email.'), - '#required' => !(!$account->getEmail() && $user->hasPermission('administer users')), + '#required' => user_mail_required($account->getEmail()), '#default_value' => (!$register ? $account->getEmail() : ''), ]; - // Only show name field if access. + // Only show name field on registration form or user can change own username. $form['account']['name'] = [ '#type' => 'textfield', '#title' => $this->t('Username'), diff -u b/core/modules/user/src/Form/UserCancelForm.php b/core/modules/user/src/Form/UserCancelForm.php --- b/core/modules/user/src/Form/UserCancelForm.php +++ b/core/modules/user/src/Form/UserCancelForm.php @@ -20,6 +20,13 @@ protected $cancelMethods; /** + * Whether allowed to select cancellation method. + * + * @var boolean + */ + protected $selectCancel; + + /** * The user being cancelled. * * @var \Drupal\user\UserInterface @@ -49,15 +56,19 @@ public function getDescription() { $description = ''; $default_method = $this->config('user.settings')->get('cancel_method'); - $admin_access = $this->currentUser()->hasPermission('administer users') || ($this->entity->id() != $this->currentUser()->id()); - if ($admin_access || $this->currentUser()->hasPermission('select account cancellation method')) { + $own_account = $this->entity->id() == $this->currentUser()->id(); + if ($this->selectCancel) { $description = $this->t('Select the method to cancel the account above.'); } // Options supplied via user_cancel_methods() can have a custom // #confirm_description property for the confirmation form description. - elseif (isset($this->cancelMethods[$default_method]['#confirm_description'])) { + // This text refers to "Your account" so only user it if cancelling own account. + elseif ($own_account && isset($this->cancelMethods[$default_method]['#confirm_description'])) { $description = $this->cancelMethods[$default_method]['#confirm_description']; } + else { + $description = $this->cancelMethods['#options'][$default_method]; + } return $description . ' ' . $this->t('This action cannot be undone.'); } @@ -76,17 +87,19 @@ $this->cancelMethods = user_cancel_methods(); // Display account cancellation method selection, if allowed. - $admin_access = $user->hasPermission('administer users') || ($this->entity->id() != $user->id()); + $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' => ($this->entity->id() == $user->id() ? $this->t('When cancelling your account') : $this->t('When cancelling the account')), - '#access' => $admin_access || $user->hasPermission('select account cancellation method'), + '#title' => $own_account ? $this->t('When cancelling your account') : $this->t('When cancelling the account'), + '#access' => $this->selectCancel, ]; $form['user_cancel_method'] += $this->cancelMethods; // When managing another user, can skip the account cancellation // confirmation mail (by default). - $override_access = $this->entity->id() != $user->id(); + $override_access = !$own_account; $form['user_cancel_confirm'] = [ '#type' => 'checkbox', '#title' => $this->t('Require email confirmation to cancel account'), @@ -111,7 +124,7 @@ // if desired. $form['access'] = [ '#type' => 'value', - '#value' => $admin_access, + '#value' => !$own_account, ]; $form = parent::buildForm($form, $form_state); diff -u b/core/modules/user/src/ProfileForm.php b/core/modules/user/src/ProfileForm.php --- b/core/modules/user/src/ProfileForm.php +++ b/core/modules/user/src/ProfileForm.php @@ -23,7 +23,7 @@ $element['delete']['#type'] = 'submit'; $element['delete']['#value'] = $this->t('Cancel account'); $element['delete']['#submit'] = ['::editCancelSubmit']; - $element['delete']['#access'] = $account->access('delete'); + $element['delete']['#access'] = $account->id() > 1 && $account->access('delete'); return $element; } reverted: --- b/core/modules/user/src/Tests/UserAdminListingTest.php +++ a/core/modules/user/src/Tests/UserAdminListingTest.php @@ -63,7 +63,7 @@ $result = $this->xpath('//table[contains(@class, "responsive-enabled")]/tbody/tr'); $result_accounts = []; foreach ($result as $account) { + $name = (string) $account->td[0]->span; - $name = (string) $account->td[0]->a; $roles = []; if (isset($account->td[2]->div->ul)) { foreach ($account->td[2]->div->ul->li as $element) { reverted: --- b/core/modules/user/src/Tests/UserAdminTest.php +++ a/core/modules/user/src/Tests/UserAdminTest.php @@ -66,12 +66,12 @@ $this->drupalGet('admin/people', ['query' => ['user' => $user_a->getUsername()]]); $result = $this->xpath('//table/tbody/tr'); $this->assertEqual(1, count($result), 'Filter by username returned the right amount.'); + $this->assertEqual($user_a->getUsername(), (string) $result[0]->td[1]->span, 'Filter by username returned the right user.'); - $this->assertEqual($user_a->getUsername(), (string) $result[0]->td[1]->a, 'Filter by username returned the right user.'); $this->drupalGet('admin/people', ['query' => ['user' => $user_a->getEmail()]]); $result = $this->xpath('//table/tbody/tr'); $this->assertEqual(1, count($result), 'Filter by username returned the right amount.'); + $this->assertEqual($user_a->getUsername(), (string) $result[0]->td[1]->span, 'Filter by username returned the right user.'); - $this->assertEqual($user_a->getUsername(), (string) $result[0]->td[1]->a, 'Filter by username returned the right user.'); // Filter the users by permission 'administer taxonomy'. $this->drupalGet('admin/people', ['query' => ['permission' => 'administer taxonomy']]); diff -u b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php --- b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php +++ b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php @@ -13,6 +13,13 @@ class UserFieldsAccessChangeTest extends UserTestBase { /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['user_access_test']; + + /** * Views used by this test. * * @var array @@ -35,8 +42,10 @@ $this->assertNoText(t('Email')); $this->assertNoText(t('Status')); - // Install user_access_test module to grant extra access. - \Drupal::service('module_installer')->install(['user_access_test']); + // Assign sub-admin role to grant extra access. + $user = $this->drupalCreateUser(['sub-admin']); + $this->drupalLogin($user); + $this->drupalGet($path); // Access for init, mail and status is added in hook_entity_field_access(). diff -u b/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php --- b/core/modules/user/src/UserAccessControlHandler.php +++ b/core/modules/user/src/UserAccessControlHandler.php @@ -42,11 +42,6 @@ return AccessResult::forbidden(); } - // The root user cannot be cancelled. - if (($entity->id() == 1) && ($operation == 'delete')) { - return AccessResult::forbidden(); - } - // Administrators can view/update/delete all user profiles. if ($account->hasPermission('administer users')) { return AccessResult::allowed()->cachePerPermissions(); diff -u b/core/modules/user/tests/modules/user_access_test/user_access_test.module b/core/modules/user/tests/modules/user_access_test/user_access_test.module --- b/core/modules/user/tests/modules/user_access_test/user_access_test.module +++ b/core/modules/user/tests/modules/user_access_test/user_access_test.module @@ -50,4 +50,12 @@ function user_access_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { + if ($field_definition->getTargetEntityTypeId() != 'user') { + return AccessResult::neutral(); + } + // Allow view access for status, init and mail fields. - return AccessResult::allowedIf($operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])); + if ($operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])) { + return AccessResult::allowedIfHasPermission($account, 'sub-admin'); + } + + return AccessResult::neutral(); } diff -u b/core/modules/user/user.module b/core/modules/user/user.module --- b/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -8,7 +8,6 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Utility\Unicode; -use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; @@ -503,12 +502,7 @@ $variables['truncated'] = FALSE; } $variables['name'] = $name; - if ($account instanceof AccessibleInterface) { - $variables['profile_access'] = $account->access('view'); - } - else { - $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles'); - } + $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles'); $external = FALSE; // Populate link path and attributes if appropriate. @@ -1453,0 +1448,19 @@ + +/** + * Determine if the user account 'mail' field is required. + * + * @param string $existing_value Existing mail value. + * + * @return boolean True if the mail field is required. + */ +function user_mail_required($existing_value) { + $required = TRUE; + + if (!$existing_value && \Drupal::currentUser()->hasPermission('administer users')) { + $required = FALSE; + } + + \Drupal::moduleHandler()->alter('user_mail_required', $required, $existing_value); + + return $required; +} diff -u b/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml --- b/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -67,7 +67,7 @@ _form: '\Drupal\user\Form\UserMultipleCancelConfirm' _title: 'Cancel user' requirements: - _permission: 'administer users' + _access: 'TRUE' entity.user_role.collection: path: '/admin/people/roles' only in patch2: unchanged: --- a/core/modules/user/src/Form/UserMultipleCancelConfirm.php +++ b/core/modules/user/src/Form/UserMultipleCancelConfirm.php @@ -141,12 +141,27 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['operation'] = ['#type' => 'hidden', '#value' => 'cancel']; + // Display account cancellation method selection, if allowed. + $user = $this->currentUser(); + $cancelMethods = user_cancel_methods(); + $selectCancel = $user->hasPermission('administer users') || $user->hasPermission('select account cancellation method'); + $form['user_cancel_method'] = [ '#type' => 'radios', '#title' => $this->t('When cancelling these accounts'), + '#access' => $selectCancel, ]; - - $form['user_cancel_method'] += user_cancel_methods(); + $form['user_cancel_method'] += $cancelMethods; + + 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' => $cancelMethods['#options'][$default_method], + ]; + } // Allow to send the account cancellation confirmation mail. $form['user_cancel_confirm'] = [ only in patch2: unchanged: --- a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequiredValidator.php +++ b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequiredValidator.php @@ -29,7 +29,7 @@ public function validate($items, Constraint $constraint) { $existing_value = $account_unchanged->getEmail(); } - $required = !(!$existing_value && \Drupal::currentUser()->hasPermission('administer users')); + $required = user_mail_required($existing_value); if ($required && (!isset($items) || $items->isEmpty())) { $this->context->addViolation($constraint->message, ['@name' => $account->getFieldDefinition('mail')->getLabel()]); only in patch2: unchanged: --- a/core/modules/user/user.permissions.yml +++ b/core/modules/user/user.permissions.yml @@ -14,7 +14,7 @@ access user profiles: change own username: title: 'Change own username' select account cancellation method: - title: 'Select method for cancelling own account' + title: 'Select method for cancelling account' restrict access: true cancel account: title: 'Cancel own user account'