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