diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php index 00cd4327bd..d9dc780b3a 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php @@ -82,7 +82,7 @@ protected function getEntityCounts() { 'search_page' => 2, 'shortcut' => 2, 'shortcut_set' => 1, - 'action' => 23, + 'action' => 24, 'menu' => 8, 'taxonomy_term' => 15, 'taxonomy_vocabulary' => 7, diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php index b1718011f0..e68a9bbc47 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php @@ -84,7 +84,7 @@ protected function getEntityCounts() { 'search_page' => 2, 'shortcut' => 6, 'shortcut_set' => 2, - 'action' => 17, + 'action' => 18, 'menu' => 6, 'taxonomy_term' => 18, 'taxonomy_vocabulary' => 4, diff --git a/core/modules/user/config/install/system.action.user_resend_action.yml b/core/modules/user/config/install/system.action.user_resend_action.yml new file mode 100644 index 0000000000..0549f3ce2f --- /dev/null +++ b/core/modules/user/config/install/system.action.user_resend_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user_resend_action +label: 'Resend welcome message the selected user(s)' +type: user +plugin: user_resend_action +configuration: { } diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml index 2f9bda44f2..426d3cda77 100644 --- a/core/modules/user/config/schema/user.schema.yml +++ b/core/modules/user/config/schema/user.schema.yml @@ -150,6 +150,10 @@ action.configuration.user_remove_role_action: type: string label: 'The ID of the role to remove' +action.configuration.user_resend_action: + type: action_configuration_default + label: 'Resend welcome message to selected users configuration' + action.configuration.user_unblock_user_action: type: action_configuration_default label: 'Unblock the selected users configuration' diff --git a/core/modules/user/src/Plugin/Action/ResendWelcomeMessage.php b/core/modules/user/src/Plugin/Action/ResendWelcomeMessage.php new file mode 100644 index 0000000000..af565b7269 --- /dev/null +++ b/core/modules/user/src/Plugin/Action/ResendWelcomeMessage.php @@ -0,0 +1,112 @@ +languageManager = $language_manager; + $this->userSettings = $config_factory->get('user.settings'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('language_manager'), + $container->get('config.factory') + ); + } + + /** + * {@inheritdoc} + */ + public function execute($account = NULL) { + if (empty($account)) { + return; + } + + $langcode = $this->languageManager->getCurrentLanguage()->getId(); + + if (!$account->isActive()) { + $op = 'register_pending_approval'; + } + else { + // Determine the user approval method. + $config = \Drupal::getContainer()->get('config.factory')->get('user.settings'); + switch ($config->get('register')) { + case UserInterface::REGISTER_ADMINISTRATORS_ONLY: + $op = 'register_admin_created'; + break; + + case UserInterface::REGISTER_VISITORS: + default: + $op = 'register_no_approval_required'; + } + } + + _user_mail_notify($op, $account, $langcode); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\user\UserInterface $object */ + $access = $object->status->access('edit', $account, TRUE) + ->andIf($object->access('update', $account, TRUE)); + + return $return_as_object ? $access : $access->isAllowed(); + } + +} diff --git a/core/modules/user/src/ProfileForm.php b/core/modules/user/src/ProfileForm.php index a1e1550360..9d5a406c8b 100644 --- a/core/modules/user/src/ProfileForm.php +++ b/core/modules/user/src/ProfileForm.php @@ -3,6 +3,7 @@ namespace Drupal\user; use Drupal\Core\Form\FormStateInterface; +use Drupal\user\UserInterface; /** * Form handler for the profile forms. @@ -25,6 +26,11 @@ protected function actions(array $form, FormStateInterface $form_state) { $element['delete']['#submit'] = ['::editCancelSubmit']; $element['delete']['#access'] = $account->id() > 1 && $account->access('delete'); + $element['resend']['#type'] = 'submit'; + $element['resend']['#value'] = $account->isActive() ? $this->t('Resend welcome message') : $this->t('Resend awaiting approval message'); + $element['resend']['#submit'] = ['::editResendSubmit']; + $element['resend']['#access'] = $account->getEmail() && $this->currentUser()->hasPermission('administer users'); + return $element; } @@ -57,4 +63,42 @@ public function editCancelSubmit($form, FormStateInterface $form_state) { ); } + /** + * Provides a submit handler for the 'Re-send welcome message' button. + */ + public function editResendSubmit(array $form, FormStateInterface $form_state) { + $account = $this->entity; + $langcode = $this->languageManager->getCurrentLanguage()->getId(); + + if (!$account->isActive()) { + $op = 'register_pending_approval'; + } + else { + // Determine the user approval method. + $config = \Drupal::getContainer()->get('config.factory')->get('user.settings'); + switch ($config->get('register')) { + case UserInterface::REGISTER_ADMINISTRATORS_ONLY: + $op = 'register_admin_created'; + break; + + case UserInterface::REGISTER_VISITORS: + default: + $op = 'register_no_approval_required'; + } + } + + // Notify the user via email. + $mail = _user_mail_notify($op, $account, $langcode); + + // Log the mail. + if (!empty($mail)) { + $this->getLogger('user')->notice('Welcome message has been re-sent to %name at %email.', ['%name' => $account->getUsername(), '%email' => $account->getEmail()]); + $this->messenger()->addMessage($this->t('Welcome message has been re-sent to %name at %email', ['%name' => $account->getUsername(), '%email' => $account->getEmail()])); + } + else { + $this->getLogger('user')->notice('There was an error re-sending welcome message to %name at %email', ['%name' => $account->getUsername(), '%email' => $account->getEmail()]); + $this->messenger()->addMessage($this->t('There was an error re-sending welcome message to %name at %email', ['%name' => $account->getUsername(), '%email' => $account->getEmail()]), 'error'); + } + } + } diff --git a/core/modules/user/tests/src/Functional/UserAdminTest.php b/core/modules/user/tests/src/Functional/UserAdminTest.php index 71a32bd760..ca8b109efc 100644 --- a/core/modules/user/tests/src/Functional/UserAdminTest.php +++ b/core/modules/user/tests/src/Functional/UserAdminTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Test\AssertMailTrait; use Drupal\Tests\BrowserTestBase; use Drupal\user\RoleInterface; +use Drupal\user\UserInterface; /** * Tests user administration page functionality. @@ -116,13 +117,13 @@ public function testUserAdmin() { $account = $user_storage->load($user_c->id()); $this->assertTrue($account->isBlocked(), 'User C blocked'); - // Test filtering on admin page for blocked users + // Test filtering on admin page for blocked users. $this->drupalGet('admin/people', ['query' => ['status' => 2]]); $this->assertNoText($user_a->getAccountName(), 'User A not on filtered by status on admin users page'); $this->assertNoText($user_b->getAccountName(), 'User B not on filtered by status on admin users page'); $this->assertText($user_c->getAccountName(), 'User C on filtered by status on admin users page'); - // Test unblocking of a user from /admin/people page and sending of activation mail + // Test unblocking of a user from /admin/people page and sending of activation mail. $editunblock = []; $editunblock['action'] = 'user_unblock_user_action'; $editunblock['user_bulk_form[4]'] = TRUE; @@ -136,7 +137,7 @@ public function testUserAdmin() { $this->assertTrue($account->isActive(), 'User C unblocked'); $this->assertMail("to", $account->getEmail(), "Activation mail sent to user C"); - // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail + // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail. $user_d = $this->drupalCreateUser([]); $user_storage->resetCache([$user_d->id()]); $account1 = $user_storage->load($user_d->id()); @@ -202,4 +203,187 @@ public function testNotificationEmailAddress() { $this->assertTrue(count($user_mail), 'New user mail to user is sent from configured Notification Email address'); } + /** + * Tests the resending of a welcome e-mail notification. + * + * @dataProvider welcomeNotifications + */ + public function testResendWelcomeEmailNotification($register_mode, $mail_id) { + $admin_user = $this->drupalCreateUser(['administer users']); + $this->drupalLogin($admin_user); + + $config = $this->config('user.settings'); + $config + ->set('register', $register_mode) + ->save(); + + // Create an active user. + $test_user = $this->drupalCreateUser(); + + // Resend a alternate welcome message. + $this->drupalPostForm('user/' . $test_user->id() . '/edit', ['status' => 0], t('Resend welcome message')); + $mails = $this->getMails(); + + // Assert only the user receive a mail. + $this->assertCount(1, $mails); + + $this->assertEqual($mails[0]['to'], $test_user->getEmail(), 'Activation mail resend to user'); + $this->assertEqual($mails[0]['id'], $mail_id); + } + + /** + * Collection of alternatives registrations configurations. + * + * @return array + * List of registration config & expected user mail. + */ + public function welcomeNotifications() { + return [ + [ + UserInterface::REGISTER_VISITORS, + 'user_register_no_approval_required', + ], + [ + UserInterface::REGISTER_ADMINISTRATORS_ONLY, + 'user_register_admin_created', + ], + [ + UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL, + 'user_register_no_approval_required', + ], + ]; + } + + /** + * Tests the resending of a waiting approval e-mail notification. + * + * @dataProvider awaitingApprovalNotifications + */ + public function testResendAwaitingApprovalEmailNotification($register_mode, $mail_id) { + $admin_user = $this->drupalCreateUser(['administer users']); + $this->drupalLogin($admin_user); + + $config = $this->config('user.settings'); + $config + ->set('register', $register_mode) + ->save(); + + // Create a blocked user. + $blocked_user = $this->drupalCreateUser()->block(); + $blocked_user->save(); + + $this->drupalPostForm('user/' . $blocked_user->id() . '/edit', ['status' => 0], t('Resend awaiting approval message')); + $mails = $this->getMails(); + + // Assert the blocked user & admin receive a mail. + $this->assertCount(2, $mails); + $this->assertEqual($mails[0]['to'], $blocked_user->getEmail(), 'Awaiting approval mail resend to user'); + $this->assertEqual($mails[0]['id'], $mail_id); + $this->assertEqual($mails[1]['to'], 'simpletest@example.com', 'Pending admin approval mail resend to admin'); + $this->assertEqual($mails[1]['id'], 'user_register_pending_approval_admin'); + } + + /** + * Collection of alternatives registrations configurations. + * + * @return array + * List of registration config & expected user mail. + */ + public function awaitingApprovalNotifications() { + return [ + [ + UserInterface::REGISTER_VISITORS, + 'user_register_pending_approval', + ], + [ + UserInterface::REGISTER_ADMINISTRATORS_ONLY, + 'user_register_pending_approval', + ], + [ + UserInterface::REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL, + 'user_register_pending_approval', + ], + ]; + } + + /** + * Tests the bulk action resending of welcome e-mails notification. + */ + public function testBulkResendWelcomeEmailsNotifications() { + $admin_user = $this->drupalCreateUser(['administer users']); + $this->drupalLogin($admin_user); + + // Create users at a certain timestamp to fix order of "Member for". + $user_a = $this->drupalCreateUser(); + $user_a->created = 1363219200; + $user_a->save(); + $user_b = $this->drupalCreateUser(); + $user_b->created = 1394755200; + $user_b->save(); + $user_c = $this->drupalCreateUser(); + $user_c->created = 1426291200; + $user_c->save(); + + // Resend welcome mail to user_c & user_b. + $edit = [ + 'action' => 'user_resend_action', + 'user_bulk_form[3]' => TRUE, + 'user_bulk_form[2]' => TRUE, + ]; + $this->drupalPostForm('admin/people', $edit, t('Apply to selected items')); + $mails = $this->getMails(); + + // Assert only 2 accounts have been notified. + $this->assertCount(2, $mails); + $this->assertEqual($mails[0]['to'], $user_c->getEmail(), 'Activation mail resend to user C'); + $this->assertEqual($mails[0]['id'], 'user_register_no_approval_required'); + $this->assertEqual($mails[1]['to'], $user_b->getEmail(), 'Activation mail resend to user B'); + $this->assertEqual($mails[1]['id'], 'user_register_no_approval_required'); + } + + /** + * Tests the bulk action resending of waiting approval e-mails notification. + */ + public function testBulkResendAwaitingApprovalEmailNotification() { + $admin_user = $this->drupalCreateUser(['administer users']); + $this->drupalLogin($admin_user); + + // Create users at a certain timestamp to fix order of "Member for". + $user_a = $this->drupalCreateUser()->block(); + $user_a->created = 1363219200; + $user_a->save(); + $user_b = $this->drupalCreateUser()->block(); + $user_b->created = 1394755200; + $user_b->save(); + $user_c = $this->drupalCreateUser()->block(); + $user_c->created = 1426291200; + $user_c->save(); + + // Resend waiting approval mail to user_c & user_b. + $edit = [ + 'action' => 'user_resend_action', + 'user_bulk_form[3]' => TRUE, + 'user_bulk_form[2]' => TRUE, + ]; + $this->drupalPostForm('admin/people', $edit, t('Apply to selected items')); + $mails = $this->getMails(); + + // Assert only 2 accounts & admin have been notified. + // The admin should be notified twice. + $this->assertCount(4, $mails); + + // Awaiting approval mail resend to user C. + $this->assertEqual($mails[0]['id'], 'user_register_pending_approval'); + $this->assertEqual($mails[0]['to'], $user_c->getEmail(), 'Awaiting approval mail resend to user C'); + // Admin notification about user C notification resend. + $this->assertEqual($mails[1]['id'], 'user_register_pending_approval_admin'); + $this->assertEqual($mails[1]['to'], 'simpletest@example.com', 'Pending admin approval mail resend to admin'); + // Awaiting approval mail resend to user B. + $this->assertEqual($mails[2]['id'], 'user_register_pending_approval'); + $this->assertEqual($mails[2]['to'], $user_b->getEmail(), 'Awaiting approval mail resend to user B'); + // Admin notification about user B notification resend. + $this->assertEqual($mails[3]['id'], 'user_register_pending_approval_admin'); + $this->assertEqual($mails[3]['to'], 'simpletest@example.com', 'Pending admin approval mail resend to admin'); + } + } diff --git a/core/modules/user/user.install b/core/modules/user/user.install index 600c2ec265..049bede6db 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -5,6 +5,8 @@ * Install, update and uninstall functions for the user module. */ +use Drupal\system\Entity\Action; + /** * Implements hook_schema(). */ @@ -103,3 +105,17 @@ function user_update_8100() { $config->set('status_blocked', $mail)->save(TRUE); } } + +/** + * Add an action to resend activation emails to multiple users. + */ +function user_update_8700() { + $action = Action::create([ + 'id' => 'user_resend_action', + 'type' => 'user', + 'label' => t('Resend welcome message the selected user(s)'), + 'configuration' => [], + 'plugin' => 'user_resend_action', + ]); + $action->trustData()->save(); +}