diff --git a/core/modules/dblog/tests/src/Functional/DbLogTest.php b/core/modules/dblog/tests/src/Functional/DbLogTest.php index 95c46392443323a036b0b8994d2526faee7ce260..b80708759473363c1ebe084e1c5897dafc7e5b5a 100644 --- a/core/modules/dblog/tests/src/Functional/DbLogTest.php +++ b/core/modules/dblog/tests/src/Functional/DbLogTest.php @@ -502,7 +502,7 @@ private function doUser(): void { // Delete the user created at the start of this test. // We need to POST here to invoke batch_process() in the internal browser. $this->drupalGet('user/' . $user->id() . '/cancel'); - $this->submitForm(['user_cancel_method' => 'user_cancel_reassign'], 'Confirm'); + $this->submitForm(['user_cancel_method' => 'user_cancel_reassign', 'edit-user-cancel-confirm' => 0], 'Confirm'); // View the database log report. $this->drupalGet('admin/reports/dblog'); diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php index 60ad0a6c917d15440fc11b8735fd4ae137beec87..6edec016ee8427ee2e3a2a401251b7f628c5ffbf 100644 --- a/core/modules/user/src/AccountSettingsForm.php +++ b/core/modules/user/src/AccountSettingsForm.php @@ -149,6 +149,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#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_confirmation'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Require email confirmation to cancel account'), + '#config_target' => 'user.settings:notify.cancel_confirm', + '#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'), + ]; $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 diff --git a/core/modules/user/src/Form/UserCancelForm.php b/core/modules/user/src/Form/UserCancelForm.php index 787339e07fd51ad03631a4fb82e13a5b2931e322..dd9578d96e026eccd46ac6d0a031951119a91e58 100644 --- a/core/modules/user/src/Form/UserCancelForm.php +++ b/core/modules/user/src/Form/UserCancelForm.php @@ -94,13 +94,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $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, + '#default_value' => $override_access === FALSE ? FALSE : $this->config('user.settings')->get('notify.cancel_confirm'), '#access' => $override_access, '#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'), ]; diff --git a/core/modules/user/src/Form/UserMultipleCancelConfirm.php b/core/modules/user/src/Form/UserMultipleCancelConfirm.php index f0463686a80c7aa6a5f59c951003750aa212fc64..240a13a5bba9853b283bc0c8e5fd37a41cf02d76 100644 --- a/core/modules/user/src/Form/UserMultipleCancelConfirm.php +++ b/core/modules/user/src/Form/UserMultipleCancelConfirm.php @@ -6,8 +6,8 @@ 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\Core\Url; use Drupal\user\UserStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -175,7 +175,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['user_cancel_confirm'] = [ '#type' => 'checkbox', '#title' => $this->t('Require email confirmation'), - '#default_value' => FALSE, + '#default_value' => $this->config('user.settings')->get('notify.cancel_confirm'), '#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'), ]; // Also allow to send account canceled notification mail, if enabled. @@ -200,13 +200,16 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Clear out the accounts from the temp store. $this->tempStoreFactory->get('user_user_operations_cancel')->delete($current_user_id); + $cancel_confirm = $form_state->getValue('user_cancel_confirm'); if ($form_state->getValue('confirm')) { + $usernames = []; 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. + /* Prevent user administrators from deleting themselves without + confirmation. */ if ($uid == $current_user_id) { $admin_form_mock = []; $admin_form_state = $form_state; @@ -221,10 +224,28 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $admin_form->submitForm($admin_form_mock, $admin_form_state); } else { - user_cancel($form_state->getValues(), $uid, $form_state->getValue('user_cancel_method')); + $user = $this->entityTypeManager->getStorage('user')->load($value); + $user->user_cancel_method = $form_state->getValue('user_cancel_method'); + $user->user_cancel_notify = $form_state->getValue('user_cancel_notify'); + $user->save(); + + if ($cancel_confirm === 1) { + _user_mail_notify('cancel_confirm', $user); + array_push($usernames, $user->label()); + } + else { + user_cancel($form_state->getValues(), $uid, $form_state->getValue('user_cancel_method')); + } + $this->logger('user')->info('Sent account cancellation request to %name %email.', + ['%name' => $user->label(), '%email' => '<' . $user->getEmail() . '>']); } } } + /* If using a cancellation confirmation email add usernames to status + message. */ + if (!empty($usernames)) { + $this->messenger()->addStatus($this->t('A confirmation request to cancel the following account(s) has been sent: %accounts.', ['%accounts' => implode(', ', $usernames)])); + } $form_state->setRedirect('entity.user.collection'); } diff --git a/core/modules/user/tests/src/Functional/UserCancelTest.php b/core/modules/user/tests/src/Functional/UserCancelTest.php index d5bdb788e6a97d78b23305e384dd09fcd6efad42..a3de9103df52b121132fb5b3c89170550332fca6 100644 --- a/core/modules/user/tests/src/Functional/UserCancelTest.php +++ b/core/modules/user/tests/src/Functional/UserCancelTest.php @@ -4,14 +4,14 @@ namespace Drupal\Tests\user\Functional; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\node\Traits\NodeAccessTrait; use Drupal\comment\CommentInterface; use Drupal\comment\Entity\Comment; use Drupal\comment\Tests\CommentTestTrait; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; -use Drupal\Tests\node\Traits\NodeAccessTrait; -use Drupal\Tests\BrowserTestBase; use Drupal\user\Entity\User; /** @@ -192,7 +192,6 @@ public function testUserBlock(): void { // Confirm account cancellation. $timestamp = time(); - $this->submitForm([], 'Confirm'); $this->assertSession()->pageTextContains('A confirmation request to cancel your account has been sent to your email address.'); @@ -279,7 +278,7 @@ public function testUserBlockUnpublish(): void { public function testUserBlockUnpublishNodeAccess(): void { \Drupal::service('module_installer')->install(['node_access_test', 'user_form_test']); - // Setup node access + // Setup node access. node_access_rebuild(); $this->addPrivateField(NodeType::load('page')); \Drupal::state()->set('node_access_test.private', TRUE); @@ -517,7 +516,9 @@ public function testUserDelete(): void { * Create an administrative user and delete another user. */ public function testUserCancelByAdmin(): void { - $this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save(); + $this->config('user.settings') + ->set('cancel_method', 'user_cancel_reassign') + ->save(); // Create a regular user. $account = $this->drupalCreateUser([]); @@ -531,6 +532,15 @@ public function testUserCancelByAdmin(): void { $this->assertSession()->pageTextContains("Are you sure you want to cancel the account {$account->getAccountName()}?"); $this->assertSession()->pageTextContains('Cancellation method'); + // Confirm the default value of the cancel_confirm checkbox is true. + $this->assertSession()->checkboxChecked('edit-user-cancel-confirm'); + + /* Now resave the configuration to false and check that the checkbox is + unchecked. */ + $this->config('user.settings')->set('notify.cancel_confirm', 0)->save(); + $this->drupalGet('user/' . $account->id() . '/cancel'); + $this->assertSession()->checkboxNotChecked('edit-user-cancel-confirm'); + // Confirm deletion. $this->submitForm([], 'Confirm'); $this->assertSession()->pageTextContains("Account {$account->getAccountName()} has been deleted."); @@ -541,7 +551,10 @@ public function testUserCancelByAdmin(): void { * Tests deletion of a user account without an email address. */ public function testUserWithoutEmailCancelByAdmin(): void { - $this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save(); + $this->config('user.settings') + ->set('cancel_method', 'user_cancel_reassign') + ->set('notify.cancel_confirm', 0) + ->save(); // Create a regular user. $account = $this->drupalCreateUser([]); @@ -569,7 +582,10 @@ public function testUserWithoutEmailCancelByAdmin(): void { */ public function testMassUserCancelByAdmin(): void { \Drupal::service('module_installer')->install(['views']); - $this->config('user.settings')->set('cancel_method', 'user_cancel_reassign')->save(); + $this->config('user.settings') + ->set('cancel_method', 'user_cancel_reassign') + ->set('notify.cancel_confirm', 0) + ->save(); $user_storage = $this->container->get('entity_type.manager')->getStorage('user'); // Enable account cancellation notification. $this->config('user.settings')->set('notify.status_canceled', TRUE)->save(); diff --git a/core/modules/user/tests/src/Functional/UserSubAdminTest.php b/core/modules/user/tests/src/Functional/UserSubAdminTest.php index c5ec390da20fdaa5240f0c154fa57c18ab642294..bd5147cf3ae1fa57e7bf98f8569d03abda2f2058 100644 --- a/core/modules/user/tests/src/Functional/UserSubAdminTest.php +++ b/core/modules/user/tests/src/Functional/UserSubAdminTest.php @@ -58,7 +58,7 @@ public function testSubAdmin(): void { $this->assertSession()->responseContains('Disable the account and keep its content.'); // Test that cancel confirmation gives an admin style message. - $this->submitForm([], 'Confirm'); + $this->submitForm(['edit-user-cancel-confirm' => 0], 'Confirm'); $this->assertSession()->pageTextContains('Account ' . $cancel_user->getAccountName() . ' has been disabled.'); // Repeat with permission to select account cancellation method. diff --git a/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php b/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php index 1841c4ab4d3eba424cbfe8bf3e4c39a49ed1d783..64dda95563e771c48d8e3965cb42115411f3ad54 100644 --- a/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php +++ b/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php @@ -123,8 +123,13 @@ public function testUserDeleteAccess(): void { $edit = [ 'user_cancel_method' => 'user_cancel_delete', ]; + $timestamp = time(); $this->submitForm($edit, 'Confirm'); + // Visit link in confirmation emails. + $this->drupalGet("user/" . $account->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account, $timestamp)); + $this->drupalGet("user/" . $account2->id() . "/cancel/confirm/$timestamp/" . user_pass_rehash($account2, $timestamp)); + // Ensure the account "no_delete" still exists. $account = User::load($account->id()); $this->assertNotNull($account, 'The user "no_delete" is not deleted.');