diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php index d695968..be70a7b 100644 --- a/core/modules/user/src/AccountForm.php +++ b/core/modules/user/src/AccountForm.php @@ -68,8 +68,17 @@ public function form(array $form, FormStateInterface $form_state) { $form['#cache']['tags'] = $config->getCacheTags(); $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + + // Check for new account. $register = $account->isAnonymous(); - $admin = $user->hasPermission('administer users'); + + // There are two sub-cases. + // - Self-register (route = 'user.register'). + // - Admin-create (route = 'user.admin_create'). + // If the current user 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'] = [ @@ -85,7 +94,7 @@ public function form(array $form, FormStateInterface $form_state) { '#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() && $admin), + '#required' => user_mail_required($account->getEmail()), '#default_value' => (!$register ? $account->getEmail() : ''), ]; @@ -103,7 +112,7 @@ public function form(array $form, FormStateInterface $form_state) { 'spellcheck' => 'false', ], '#default_value' => (!$register ? $account->getAccountName() : ''), - '#access' => ($register || ($user->id() == $account->id() && $user->hasPermission('change own username')) || $admin), + '#access' => $account->name->access('edit', $user, TRUE), ]; // Display password field only for existing users or when user is allowed to @@ -150,7 +159,7 @@ public function form(array $form, FormStateInterface $form_state) { } } } - elseif (!$config->get('verify_mail') || $admin) { + elseif (!$config->get('verify_mail') || $admin_create) { $form['account']['pass'] = [ '#type' => 'password_confirm', '#size' => 25, @@ -169,7 +178,7 @@ public function form(array $form, FormStateInterface $form_state) { } } - if ($admin || !$register) { + if (!$self_register) { $status = $account->get('status')->value; } else { @@ -181,7 +190,7 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Status'), '#default_value' => $status, '#options' => [$this->t('Blocked'), $this->t('Active')], - '#access' => $admin, + '#access' => $account->status->access('edit', $user, TRUE), ]; $roles = array_map(['\Drupal\Component\Utility\Html', 'escape'], user_role_names(TRUE)); @@ -203,7 +212,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['account']['notify'] = [ '#type' => 'checkbox', '#title' => $this->t('Notify user of new account'), - '#access' => $register && $admin, + '#access' => $admin_create, ]; $user_preferred_langcode = $register ? $language_interface->getId() : $account->getPreferredLangcode(); @@ -222,7 +231,7 @@ public function form(array $form, FormStateInterface $form_state) { '#open' => TRUE, // Display language selector when either creating a user on the admin // interface or editing a user account. - '#access' => !$register || $admin, + '#access' => !$self_register, ]; $form['language']['preferred_langcode'] = [ diff --git a/core/modules/user/src/Form/UserCancelForm.php b/core/modules/user/src/Form/UserCancelForm.php index da7a5f4..1cb99e8 100644 --- a/core/modules/user/src/Form/UserCancelForm.php +++ b/core/modules/user/src/Form/UserCancelForm.php @@ -20,6 +20,13 @@ class UserCancelForm extends ContentEntityConfirmFormBase { protected $cancelMethods; /** + * Whether allowed to select cancellation method. + * + * @var boolean + */ + protected $selectCancel; + + /** * The user being cancelled. * * @var \Drupal\user\UserInterface @@ -49,14 +56,19 @@ public function getCancelUrl() { public function getDescription() { $description = ''; $default_method = $this->config('user.settings')->get('cancel_method'); - if ($this->currentUser()->hasPermission('administer users') || $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.'); } @@ -75,18 +87,19 @@ public function buildForm(array $form, FormStateInterface $form_state) { $this->cancelMethods = user_cancel_methods(); // Display account cancellation method selection, if allowed. - $admin_access = $user->hasPermission('administer users'); + $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; - // Allow user administrators to skip the account cancellation confirmation - // mail (by default), as long as they do not attempt to cancel their own - // account. - $override_access = $admin_access && ($this->entity->id() != $user->id()); + // 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 to cancel account'), @@ -111,7 +124,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // if desired. $form['access'] = [ '#type' => 'value', - '#value' => $user->hasPermission('administer users'), + '#value' => !$own_account, ]; $form = parent::buildForm($form, $form_state); diff --git a/core/modules/user/src/Form/UserMultipleCancelConfirm.php b/core/modules/user/src/Form/UserMultipleCancelConfirm.php index 985d199..5f38ef0 100644 --- 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'] = [ diff --git a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequiredValidator.php b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequiredValidator.php index 7e20b7a..420c003 100644 --- 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()]); diff --git a/core/modules/user/src/ProfileForm.php b/core/modules/user/src/ProfileForm.php index c856b75..9a35772 100644 --- a/core/modules/user/src/ProfileForm.php +++ b/core/modules/user/src/ProfileForm.php @@ -20,12 +20,10 @@ protected function actions(array $form, FormStateInterface $form_state) { // The user account being edited. $account = $this->entity; - // The user doing the editing. - $user = $this->currentUser(); $element['delete']['#type'] = 'submit'; $element['delete']['#value'] = $this->t('Cancel account'); $element['delete']['#submit'] = ['::editCancelSubmit']; - $element['delete']['#access'] = $account->id() > 1 && (($account->id() == $user->id() && $user->hasPermission('cancel account')) || $user->hasPermission('administer users')); + $element['delete']['#access'] = $account->id() > 1 && $account->access('delete'); return $element; } diff --git a/core/modules/user/src/RegisterForm.php b/core/modules/user/src/RegisterForm.php index 4974e69..cf046cc 100644 --- a/core/modules/user/src/RegisterForm.php +++ b/core/modules/user/src/RegisterForm.php @@ -18,7 +18,14 @@ public function form(array $form, FormStateInterface $form_state) { $user = $this->currentUser(); /** @var \Drupal\user\UserInterface $account */ $account = $this->entity; - $admin = $user->hasPermission('administer users'); + + // This form is used for two cases: + // - Self-register (route = 'user.register'). + // - Admin-create (route = 'user.admin_create'). + // If the current user has permission to create users then it must be the + // second case. + $admin = $account->access('create'); + // Pass access information to the submit handler. Running an access check // inside the submit function interferes with form processing and breaks // hook_form_alter(). @@ -29,7 +36,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['#attached']['library'][] = 'core/drupal.form'; - // For non-admin users, populate the form fields using data from the + // For users editing their own account, populate the form fields using data from the // browser. if (!$admin) { $form['#attributes']['data-user-info-from-browser'] = TRUE; diff --git a/core/modules/user/src/Tests/UserSubAdminTest.php b/core/modules/user/src/Tests/UserSubAdminTest.php new file mode 100644 index 0000000..1102a32 --- /dev/null +++ b/core/modules/user/src/Tests/UserSubAdminTest.php @@ -0,0 +1,66 @@ +drupalCreateUser(['sub-admin']); + $this->drupalLogin($user); + + // Test that the create user page has admin fields. + $this->drupalGet('admin/people/create'); + $this->assertField("edit-name", "Name field exists."); + $this->assertField("edit-notify", "Notify field exists."); + + // Not 'status' or 'roles' as they require extra permission. + $this->assertNoField("edit-status-0", "Status field missing."); + $this->assertNoField("edit-role", "Role field missing."); + + // Test that create user gives an admin style message. + $edit = [ + 'name' => $this->randomMachineName(), + 'mail' => $this->randomMachineName() . '@example.com', + 'pass[pass1]' => $pass = $this->randomString(), + 'pass[pass2]' => $pass, + 'notify' => FALSE, + ]; + $this->drupalPostForm('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for @name. No email has been sent.', ['@name' => $edit['name']]), 'User created'); + + // Test that the cancel user page has admin fields. + $cancel_user = $this->createUser(); + $this->drupalGet('user/' . $cancel_user->id() . '/cancel'); + $this->assertRaw(t('Are you sure you want to cancel the account %name?', ['%name' => $cancel_user->getUsername()]), 'Confirmation form to cancel account displayed.'); + $this->assertRaw(t('Disable the account and keep its content.') . ' ' . t('This action cannot be undone.'), 'Cannot select account cancellation method.'); + + // Test that cancel confirmation gives an admin style message. + $this->drupalPostForm(NULL, NULL, t('Cancel account')); + $this->assertRaw(t('%name has been disabled.', ['%name' => $cancel_user->getUsername()]), "Confirmation message displayed to user."); + + // Repeat with permission to select account cancellation method. + $user->addRole($this->drupalCreateRole(['select account cancellation method'])); + $user->save(); + $cancel_user = $this->createUser(); + $this->drupalGet('user/' . $cancel_user->id() . '/cancel'); + $this->assertText(t('Select the method to cancel the account above.'), 'Allows to select account cancellation method.'); + } + +} diff --git a/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php new file mode 100644 index 0000000..f0e40cf --- /dev/null +++ b/core/modules/user/src/Tests/Views/UserFieldsAccessChangeTest.php @@ -0,0 +1,54 @@ +drupalGet($path); + + // User has access to name and created date by default. + $this->assertText(t('Name')); + $this->assertText(t('Created')); + + // User does not by default have access to init, mail and status. + $this->assertNoText(t('Init')); + $this->assertNoText(t('Email')); + $this->assertNoText(t('Status')); + + // 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(). + $this->assertText(t('Init')); + $this->assertText(t('Email')); + $this->assertText(t('Status')); + } + +} diff --git a/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php index 712b32a..03804cd 100644 --- a/core/modules/user/src/UserAccessControlHandler.php +++ b/core/modules/user/src/UserAccessControlHandler.php @@ -93,11 +93,9 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_ $is_own_account = $items ? $items->getEntity()->id() == $account->id() : FALSE; switch ($field_definition->getName()) { case 'name': - // Allow view access to anyone with access to the entity. Anonymous - // users should be able to access the username field during the - // registration process, otherwise the username and email constraints - // are not checked. - if ($operation == 'view' || ($items && $account->isAnonymous() && $items->getEntity()->isAnonymous())) { + // Allow view access to anyone with access to the entity. + // The username field is editable during the registration process. + if ($operation == 'view' || ($items && $items->getEntity()->isAnonymous())) { return AccessResult::allowed()->cachePerPermissions(); } // Allow edit access for the own user name if the permission is @@ -106,7 +104,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_ return AccessResult::allowed()->cachePerPermissions()->cachePerUser(); } else { - return AccessResult::forbidden(); + return AccessResult::neutral(); } case 'preferred_langcode': @@ -116,7 +114,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_ // Allow view access to own mail address and other personalization // settings. if ($operation == 'view') { - return $is_own_account ? AccessResult::allowed()->cachePerUser() : AccessResult::forbidden(); + return $is_own_account ? AccessResult::allowed()->cachePerUser() : AccessResult::neutral(); } // Anyone that can edit the user can also edit this field. return AccessResult::allowed()->cachePerPermissions(); @@ -127,14 +125,14 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_ case 'created': // Allow viewing the created date, but not editing it. - return ($operation == 'view') ? AccessResult::allowed() : AccessResult::forbidden(); + return ($operation == 'view') ? AccessResult::allowed() : AccessResult::neutral(); case 'roles': case 'status': case 'access': case 'login': case 'init': - return AccessResult::forbidden(); + return AccessResult::neutral(); } return parent::checkFieldAccess($operation, $field_definition, $account, $items); diff --git a/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 index 470a76a..4369ac8 100644 --- a/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 @@ -6,6 +6,9 @@ */ use Drupal\Core\Access\AccessResult; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\user\Entity\User; /** @@ -20,5 +23,39 @@ function user_access_test_user_access(User $entity, $operation, $account) { // Deny delete access. return AccessResult::forbidden(); } + + // Account with role sub-admin can manage users with no roles. + if (count($entity->getRoles()) == 1) { + return AccessResult::allowedIfHasPermission($account, 'sub-admin'); + } + + return AccessResult::neutral(); +} + +/** + * Implements hook_entity_create_access(). + */ +function user_access_test_entity_create_access(AccountInterface $account, array $context, $entity_bundle) { + if ($context['entity_type_id'] != 'user') { + return AccessResult::neutral(); + } + + // Account with role sub-admin can create users. + return AccessResult::allowedIfHasPermission($account, 'sub-admin'); +} + +/** + * Implements hook_entity_field_access(). + */ +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. + if ($operation === 'view' && in_array($field_definition->getName(), ['status', 'init', 'mail'])) { + return AccessResult::allowedIfHasPermission($account, 'sub-admin'); + } + return AccessResult::neutral(); } diff --git a/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml b/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml new file mode 100644 index 0000000..dadf7ba --- /dev/null +++ b/core/modules/user/tests/modules/user_access_test/user_access_test.permissions.yml @@ -0,0 +1,2 @@ +sub-admin: + title: 'Administer users with no roles' diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml new file mode 100644 index 0000000..5bad08f --- /dev/null +++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml @@ -0,0 +1,478 @@ +langcode: en +status: true +dependencies: + module: + - user +id: test_user_fields_access +label: '' +module: views +description: '' +tag: '' +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + name: name + status: status + mail: mail + init: init + created: created + info: + name: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + mail: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + init: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: '-1' + empty_table: false + row: + type: fields + fields: + name: + id: name + table: users_field_data + field: name + relationship: none + group_type: group + admin_label: '' + label: Name + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: false + ellipsis: false + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: user_name + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: name + plugin_id: field + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: default + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: status + plugin_id: field + mail: + id: mail + table: users_field_data + field: mail + relationship: none + group_type: group + admin_label: '' + label: Email + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: basic_string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: mail + plugin_id: field + init: + id: init + table: users_field_data + field: init + relationship: none + group_type: group + admin_label: '' + label: Init + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: basic_string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: init + plugin_id: field + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + label: Created + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: created + plugin_id: field + filters: { } + sorts: { } + title: '' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test_user_fields_access + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 2d7d24a..d57aaac 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1445,3 +1445,23 @@ function template_preprocess_user(&$variables) { $variables['content'][$key] = $variables['elements'][$key]; } } + +/** + * 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 --git a/core/modules/user/user.permissions.yml b/core/modules/user/user.permissions.yml index 810583c..a295b1f 100644 --- 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' diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index e38bb7a..f22e002 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -43,7 +43,7 @@ user.admin_create: _entity_form: 'user.register' _title: 'Add user' requirements: - _permission: 'administer users' + _entity_create_access: 'user' user.admin_permissions: path: '/admin/people/permissions' @@ -67,7 +67,7 @@ user.multiple_cancel_confirm: _form: '\Drupal\user\Form\UserMultipleCancelConfirm' _title: 'Cancel user' requirements: - _permission: 'administer users' + _access: 'TRUE' entity.user_role.collection: path: '/admin/people/roles'