From 346e67435008b91b3d4c2f5c5667283169ce357b Mon Sep 17 00:00:00 2001 From: mdranove <mdranove1@gmail.com> Date: Wed, 12 Feb 2025 19:53:19 -0500 Subject: [PATCH 1/5] Make use of notify.cancel_confirm config. Update tests. --- core/modules/user/src/AccountSettingsForm.php | 6 +++++ core/modules/user/src/Form/UserCancelForm.php | 2 +- .../src/Form/UserMultipleCancelConfirm.php | 24 +++++++++++++++---- .../tests/src/Functional/UserCancelTest.php | 16 +++++++++---- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php index 60ad0a6c917d..6edec016ee84 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 787339e07fd5..9b4f5839ccad 100644 --- a/core/modules/user/src/Form/UserCancelForm.php +++ b/core/modules/user/src/Form/UserCancelForm.php @@ -100,7 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['user_cancel_confirm'] = [ '#type' => 'checkbox', '#title' => $this->t('Require email confirmation'), - '#default_value' => !$override_access, + '#default_value' => $override_access === TRUE ? 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 f0463686a80c..8de0a7fa5df5 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,7 +224,20 @@ 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); + } + else { + user_cancel($form_state->getValues(), $uid, $form_state->getValue('user_cancel_method')); + } + array_push($usernames, $user->label()); + $this->logger('user')->info('Sent account cancellation request to %name %email.', + ['%name' => $user->label(), '%email' => '<' . $user->getEmail() . '>']); } } } diff --git a/core/modules/user/tests/src/Functional/UserCancelTest.php b/core/modules/user/tests/src/Functional/UserCancelTest.php index d5bdb788e6a9..b9dbf070aae1 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; /** @@ -279,7 +279,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 +517,10 @@ 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') + ->set('notify.cancel_confirm', 0) + ->save(); // Create a regular user. $account = $this->drupalCreateUser([]); @@ -569,7 +572,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(); -- GitLab From db87760d1588ce54b3356d7e8a62d72d1e9526fc Mon Sep 17 00:00:00 2001 From: mdranove <mdranove1@gmail.com> Date: Wed, 12 Feb 2025 20:22:30 -0500 Subject: [PATCH 2/5] Click the cancel confirmation emails in BulkFormAccessTest.php. --- .../user/tests/src/Functional/Views/BulkFormAccessTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php b/core/modules/user/tests/src/Functional/Views/BulkFormAccessTest.php index 1841c4ab4d3e..64dda95563e7 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.'); -- GitLab From a2cbe35aa5cfb9c982aa517f19dee741bd2720b1 Mon Sep 17 00:00:00 2001 From: mdranove <mdranove1@gmail.com> Date: Wed, 12 Feb 2025 22:32:53 -0500 Subject: [PATCH 3/5] Check for notify.cancel_confirm default_value in tests. Fix logic in UserCancelForm.php. Disable cancel confirm in a few tests. --- .../dblog/tests/src/Functional/DbLogTest.php | 2 +- core/modules/user/src/Form/UserCancelForm.php | 4 +--- .../user/tests/src/Functional/UserCancelTest.php | 16 +++++++++++++--- .../tests/src/Functional/UserSubAdminTest.php | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/core/modules/dblog/tests/src/Functional/DbLogTest.php b/core/modules/dblog/tests/src/Functional/DbLogTest.php index 95c463924433..b80708759473 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/Form/UserCancelForm.php b/core/modules/user/src/Form/UserCancelForm.php index 9b4f5839ccad..dd9578d96e02 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 === TRUE ? FALSE : $this->config('user.settings')->get('notify.cancel_confirm'), + '#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/tests/src/Functional/UserCancelTest.php b/core/modules/user/tests/src/Functional/UserCancelTest.php index b9dbf070aae1..a3de9103df52 100644 --- a/core/modules/user/tests/src/Functional/UserCancelTest.php +++ b/core/modules/user/tests/src/Functional/UserCancelTest.php @@ -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.'); @@ -519,7 +518,6 @@ public function testUserDelete(): void { public function testUserCancelByAdmin(): void { $this->config('user.settings') ->set('cancel_method', 'user_cancel_reassign') - ->set('notify.cancel_confirm', 0) ->save(); // Create a regular user. @@ -534,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."); @@ -544,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([]); diff --git a/core/modules/user/tests/src/Functional/UserSubAdminTest.php b/core/modules/user/tests/src/Functional/UserSubAdminTest.php index c5ec390da20f..bd5147cf3ae1 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. -- GitLab From 0935d3a4b7b196871ad7725b0ffcab0d6c7752bd Mon Sep 17 00:00:00 2001 From: mdranove <mdranove1@gmail.com> Date: Thu, 13 Feb 2025 09:05:01 -0500 Subject: [PATCH 4/5] Add confirmation message to cancellation confirm action in UserMultipleCancelConfirm.php. --- core/modules/user/src/Form/UserMultipleCancelConfirm.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/modules/user/src/Form/UserMultipleCancelConfirm.php b/core/modules/user/src/Form/UserMultipleCancelConfirm.php index 8de0a7fa5df5..c2ba78e15f9e 100644 --- a/core/modules/user/src/Form/UserMultipleCancelConfirm.php +++ b/core/modules/user/src/Form/UserMultipleCancelConfirm.php @@ -241,6 +241,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } } } + /* 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'); } -- GitLab From e18cb3ceb817fa0079431f5a776d17afc24a279b Mon Sep 17 00:00:00 2001 From: mdranove <mdranove1@gmail.com> Date: Thu, 13 Feb 2025 09:37:41 -0500 Subject: [PATCH 5/5] Only push usernames to username array if cancel_confirm true. --- core/modules/user/src/Form/UserMultipleCancelConfirm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/user/src/Form/UserMultipleCancelConfirm.php b/core/modules/user/src/Form/UserMultipleCancelConfirm.php index c2ba78e15f9e..240a13a5bba9 100644 --- a/core/modules/user/src/Form/UserMultipleCancelConfirm.php +++ b/core/modules/user/src/Form/UserMultipleCancelConfirm.php @@ -231,11 +231,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) { 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')); } - array_push($usernames, $user->label()); $this->logger('user')->info('Sent account cancellation request to %name %email.', ['%name' => $user->label(), '%email' => '<' . $user->getEmail() . '>']); } -- GitLab