From 699daafc64a17ab44d2c711446d4284eab69da8b Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Thu, 11 Jul 2024 07:57:33 +0700 Subject: [PATCH] MDL-64148 core: Make email greetings consistent Some email body strings use first names as greetings, some use full names, and some do not. Using the first name for greeting makes it consistent and a bit more "personal". --- .upgradenotes/MDL-64148-2024071508585319.yml | 6 +++ .../manual/tests/behat/welcomemessage.feature | 6 +-- enrol/manual/tests/lib_test.php | 2 +- enrol/self/tests/behat/welcomemessage.feature | 6 +-- enrol/tests/enrollib_test.php | 41 +++++++++++++++++++ lang/en/admin.php | 4 +- lang/en/auth.php | 2 +- lang/en/enrol.php | 2 +- lang/en/moodle.php | 6 +-- lib/classes/user.php | 16 ++++++++ lib/enrollib.php | 6 ++- lib/moodlelib.php | 22 +++++++--- lib/tests/authlib_test.php | 18 ++++++++ lib/tests/moodlelib_test.php | 41 +++++++++++++++++++ user/edit.php | 7 +++- user/tests/userlib_test.php | 35 ++++++++++++++++ 16 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 .upgradenotes/MDL-64148-2024071508585319.yml diff --git a/.upgradenotes/MDL-64148-2024071508585319.yml b/.upgradenotes/MDL-64148-2024071508585319.yml new file mode 100644 index 0000000000000..55d7346f6b4b1 --- /dev/null +++ b/.upgradenotes/MDL-64148-2024071508585319.yml @@ -0,0 +1,6 @@ +issueNumber: MDL-64148 +notes: + core: + - message: >- + Add \core_user::get_name_placeholders() to return an array of user name fields. + type: improved diff --git a/enrol/manual/tests/behat/welcomemessage.feature b/enrol/manual/tests/behat/welcomemessage.feature index 6beda6759060d..a5434619bf738 100644 --- a/enrol/manual/tests/behat/welcomemessage.feature +++ b/enrol/manual/tests/behat/welcomemessage.feature @@ -29,7 +29,7 @@ Feature: A course welcome message will be sent to the user when they are enrolle Then I should see "Send course welcome message" And the field "Send course welcome message" matches value "From the course contact" And I should see "Custom welcome message" - And the field "Custom welcome message" matches value "Dear {$a->fullname}, you have successfully been enrolled to course {$a->coursename}" + And the field "Custom welcome message" matches value "Hi {$a->firstname}, you have successfully been enrolled to course {$a->coursename}" And I should see "Accepted formats: Plain text or Moodle-auto format. HTML tags and multi-lang tags are also accepted, as well as the following placeholders:" And I set the field "Send course welcome message" to "No" And I should not see "Custom welcome message" @@ -87,14 +87,14 @@ Feature: A course welcome message will be sent to the user when they are enrolle And I open the notification popover And I should see "Welcome to Course 1" And I click on "View full notification" "link" in the ".popover-region-notifications" "css_element" - And I should see "Dear First User, you have successfully been enrolled to course Course 1" + And I should see "Hi First, you have successfully been enrolled to course Course 1" # Login as second user and check the notification. And I am on the "C1" "course" page logged in as user2 And I should see "1" in the "#nav-notification-popover-container [data-region='count-container']" "css_element" And I open the notification popover And I should see "Welcome to Course 2" And I click on "View full notification" "link" in the ".popover-region-notifications" "css_element" - And I should see "Dear Second User, you have successfully been enrolled to course Course 2" + And I should see "Hi Second, you have successfully been enrolled to course Course 2" @javascript Scenario: Students should receive a welcome message if the setting is enabled - Custom message diff --git a/enrol/manual/tests/lib_test.php b/enrol/manual/tests/lib_test.php index 567e5c246dd57..576ac36e4c125 100644 --- a/enrol/manual/tests/lib_test.php +++ b/enrol/manual/tests/lib_test.php @@ -843,7 +843,7 @@ public function test_send_course_welcome_message(): void { get_string( 'customwelcomemessageplaceholder', 'core_enrol', - ['fullname' => fullname($student), 'coursename' => $course->fullname], + ['firstname' => $student->firstname, 'coursename' => $course->fullname], ), $message->fullmessage, ); diff --git a/enrol/self/tests/behat/welcomemessage.feature b/enrol/self/tests/behat/welcomemessage.feature index adf2e9f561239..56fe810c1cc7e 100644 --- a/enrol/self/tests/behat/welcomemessage.feature +++ b/enrol/self/tests/behat/welcomemessage.feature @@ -34,7 +34,7 @@ Feature: A course welcome message will be sent to the user when they auto-enrol Then I should see "Send course welcome message" And the field "Send course welcome message" matches value "From the course contact" And I should see "Custom welcome message" - And the field "Custom welcome message" matches value "Dear {$a->fullname}, you have successfully been enrolled to course {$a->coursename}" + And the field "Custom welcome message" matches value "Hi {$a->firstname}, you have successfully been enrolled to course {$a->coursename}" And I should see "Accepted formats: Plain text or Moodle-auto format. HTML tags and multi-lang tags are also accepted, as well as the following placeholders:" And I set the field "Send course welcome message" to "No" And I should not see "Custom welcome message" @@ -73,7 +73,7 @@ Feature: A course welcome message will be sent to the user when they auto-enrol And I open the notification popover And I should see "Welcome to Course 1" And I click on "View full notification" "link" in the ".popover-region-notifications" "css_element" - And I should see "Dear First User, you have successfully been enrolled to course Course 1" + And I should see "Hi First, you have successfully been enrolled to course Course 1" # Login as second user and check the notification. And I log in as "user2" And I am on "Course 2" course homepage @@ -82,7 +82,7 @@ Feature: A course welcome message will be sent to the user when they auto-enrol And I open the notification popover And I should see "Welcome to Course 2" And I click on "View full notification" "link" in the ".popover-region-notifications" "css_element" - And I should see "Dear Second User, you have successfully been enrolled to course Course 2" + And I should see "Hi Second, you have successfully been enrolled to course Course 2" @javascript Scenario: Students should receive a welcome message if the setting is enabled - Custom message diff --git a/enrol/tests/enrollib_test.php b/enrol/tests/enrollib_test.php index 654d4fe1e4be6..6d5f90c73890f 100644 --- a/enrol/tests/enrollib_test.php +++ b/enrol/tests/enrollib_test.php @@ -1827,4 +1827,45 @@ public function test_update_enrol_plugin_data(): void { $modifiedinstance = $manualplugin->update_enrol_plugin_data($course->id, $enrolmentdata, $instance); $this->assertEquals($expectedinstance, $modifiedinstance); } + + /** + * Test case for checking the email greetings in various user notification emails. + * + * @covers \enrol_plugin::send_course_welcome_message_to_user + */ + public function test_email_greetings(): void { + global $DB; + $this->resetAfterTest(); + + // Create course. + $course = $this->getDataGenerator()->create_course([ + 'fullname' => 'Course 1', + 'shortname' => 'C1', + ]); + // Create user. + $student = $this->getDataGenerator()->create_user(); + // Get manual plugin. + $manualplugin = enrol_get_plugin('manual'); + $maninstance = $DB->get_record( + 'enrol', + ['courseid' => $course->id, 'enrol' => 'manual'], + '*', + MUST_EXIST, + ); + + $messagesink = $this->redirectMessages(); + $manualplugin->send_course_welcome_message_to_user( + instance: $maninstance, + userid: $student->id, + sendoption: ENROL_SEND_EMAIL_FROM_NOREPLY, + message: '', + ); + $messages = $messagesink->get_messages_by_component_and_type( + 'moodle', + 'enrolcoursewelcomemessage', + ); + $this->assertNotEmpty($messages); + $message = reset($messages); + $this->assertStringContainsString('Hi ' . $student->firstname, quoted_printable_decode($message->fullmessage)); + } } diff --git a/lang/en/admin.php b/lang/en/admin.php index db918e07cbd86..47fe143a29c23 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -815,7 +815,9 @@ $string['locked'] = 'Locked'; $string['lockoutduration'] = 'Account lockout duration'; $string['lockoutduration_desc'] = 'Locked out account is automatically unlocked after this duration.'; -$string['lockoutemailbody'] = 'Your account with username {$a->username} on server \'{$a->sitename}\' +$string['lockoutemailbody'] = 'Hi {$a->firstname}, + +Your account with username {$a->username} on server \'{$a->sitename}\' was locked out after multiple invalid login attempts. To unlock the account immediately go to the following address diff --git a/lang/en/auth.php b/lang/en/auth.php index 0098a30741160..3f879bee3c305 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -77,7 +77,7 @@ $string['emailchangepending'] = 'Change pending. Open the link sent to you at {$a->preference_newemail}.'; $string['emailnowexists'] = 'The email address you tried to assign to your profile has been assigned to someone else since your original request. Your request for change of email address is hereby cancelled, but you may try again with a different address.'; $string['emailupdate'] = 'Email address update'; -$string['emailupdatemessage'] = 'Dear {$a->fullname}, +$string['emailupdatemessage'] = 'Hi {$a->firstname}, You have requested a change of your email address for your account on {$a->site}. To confirm this change, please go to the following web address: diff --git a/lang/en/enrol.php b/lang/en/enrol.php index 4b19f85ca201c..820a75e5ad153 100644 --- a/lang/en/enrol.php +++ b/lang/en/enrol.php @@ -45,7 +45,7 @@
  • User last name {$a->lastname}
  • User course role {$a->courserole}
  • '; -$string['customwelcomemessageplaceholder'] = 'Dear {$a->fullname}, you have successfully been enrolled to course {$a->coursename}'; +$string['customwelcomemessageplaceholder'] = 'Hi {$a->firstname}, you have successfully been enrolled to course {$a->coursename}'; $string['defaultenrol'] = 'Add instance to new courses'; $string['defaultenrol_desc'] = 'It is possible to add this plugin to all new courses by default.'; $string['deleteinstanceconfirm'] = 'You are about to delete the enrolment method "{$a->name}". All {$a->users} users currently enrolled using this method will be unenrolled and any course-related data such as users\' grades, group membership or forum subscriptions will be deleted. diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 11600825284eb..a3f1ed6332163 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -643,7 +643,7 @@ $string['emailactive'] = 'Email activated'; $string['emailagain'] = 'Email (again)'; $string['emailconfirm'] = 'Confirm your account'; -$string['emailconfirmation'] = 'Hi, +$string['emailconfirmation'] = 'Hi {$a->firstname}, A new account has been requested at \'{$a->sitename}\' using your email address. @@ -2501,8 +2501,6 @@ // Deprecated since Moodle 4.5. $string['commentscount'] = 'Comments ({$a})'; $string['datechanged'] = 'Date changed'; -$string['registrationcontactno'] = 'No, I do not want to be contacted by other people'; -$string['registrationcontactyes'] = 'Yes, provide a form for other Moodlers to contact me'; $string['newpasswordtext'] = 'Hi {$a->firstname}, Your account password at \'{$a->sitename}\' has been reset @@ -2522,3 +2520,5 @@ Cheers from the \'{$a->sitename}\' administrator, {$a->signoff}'; +$string['registrationcontactno'] = 'No, I do not want to be contacted by other people'; +$string['registrationcontactyes'] = 'Yes, provide a form for other Moodlers to contact me'; diff --git a/lib/classes/user.php b/lib/classes/user.php index c411d5f86aba5..1f1a0be23ee33 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -1589,4 +1589,20 @@ public static function get_users_search_sql(context $context, string $usersearch ]; } + /** + * Generates an array of name placeholders for a given user. + * + * @param stdClass $user The user object containing name fields. + * @return array An associative array of name placeholders. + */ + public static function get_name_placeholders(stdClass $user): array { + $namefields = [ + 'fullname' => fullname($user), + 'alternativefullname' => fullname($user, true), + ]; + foreach (fields::get_name_fields() as $namefield) { + $namefields[$namefield] = $user->{$namefield}; + } + return $namefields; + } } diff --git a/lib/enrollib.php b/lib/enrollib.php index 7b2cac48c1ca4..fa97842e660ec 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -3670,7 +3670,11 @@ public function send_course_welcome_message_to_user( 'course' => $instance->courseid, ], ))->out(); - $a->fullname = fullname($user); + + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $a->{$field} = $value; + } if ($message && trim($message) !== '') { $placeholders = [ diff --git a/lib/moodlelib.php b/lib/moodlelib.php index ba6415a77d78b..781c33e83f83c 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -6039,7 +6039,10 @@ function setnew_password_and_mail($user, $fasthash = false) { update_internal_user_password($user, $newpassword, $fasthash); $a = new stdClass(); - $a->firstname = fullname($user, true); + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $a->{$field} = $value; + } $a->sitename = format_string($site->fullname); $a->username = $user->username; $a->newpassword = $newpassword; @@ -6071,6 +6074,11 @@ function send_confirmation_email($user, $confirmationurl = null) { $data = new stdClass(); $data->sitename = format_string($site->fullname); $data->admin = generate_email_signoff(); + // Add user name fields to $data based on $user. + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $data->{$field} = $value; + } $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname)); @@ -6116,8 +6124,10 @@ function send_password_change_confirmation_email($user, $resetrecord) { $pwresetmins = isset($CFG->pwresettime) ? floor($CFG->pwresettime / MINSECS) : 30; $data = new stdClass(); - $data->firstname = $user->firstname; - $data->lastname = $user->lastname; + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $data->{$field} = $value; + } $data->username = $user->username; $data->sitename = format_string($site->fullname); $data->link = $CFG->wwwroot .'/login/forgot_password.php?token='. $resetrecord->token; @@ -6143,8 +6153,10 @@ function send_password_change_info($user) { $supportuser = core_user::get_support_user(); $data = new stdClass(); - $data->firstname = $user->firstname; - $data->lastname = $user->lastname; + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $data->{$field} = $value; + } $data->username = $user->username; $data->sitename = format_string($site->fullname); $data->admin = generate_email_signoff(); diff --git a/lib/tests/authlib_test.php b/lib/tests/authlib_test.php index 8a053ed6cf077..c6be2bce21ab1 100644 --- a/lib/tests/authlib_test.php +++ b/lib/tests/authlib_test.php @@ -549,4 +549,22 @@ public function test_get_enabled_auth_plugin_classes(): void { $this->assertEquals(count($plugins), 3); } + /** + * Test case for checking the email greetings in account lockout notification emails. + * + * @covers ::login_lock_account() + */ + public function test_email_greetings(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + + $sink = $this->redirectEmails(); // Make sure we are redirecting emails. + login_lock_account($user); + $result = $sink->get_messages(); + $sink->close(); + // Test greetings. + $this->assertStringContainsString('Hi ' . $user->firstname, quoted_printable_decode($result[0]->body)); + } + } diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 750c8a3f6b779..b3b89439f4a74 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -5593,4 +5593,45 @@ public static function moodle_array_keys_filter_provider(): array { ], ]; } + + /** + * Test case for checking the email greetings in various user notification emails. + * + * @dataProvider email_greetings_provider + * @param string $funcname The name of the function to call for sending the email. + * @param mixed $extra Any extra parameter required by the function. + * @covers ::send_password_change_info() + * @covers ::send_confirmation_email() + * @covers ::setnew_password_and_mail() + * @covers ::send_password_change_confirmation_email() + */ + public function test_email_greetings($funcname, $extra): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + + $sink = $this->redirectEmails(); // Make sure we are redirecting emails. + $funcname($user, $extra); + $result = $sink->get_messages(); + $sink->close(); + // Test greetings. + $this->assertStringContainsString('Hi ' . $user->firstname, quoted_printable_decode($result[0]->body)); + } + + /** + * Data provider for test_email_greetings tests. + * + * @return array + */ + public static function email_greetings_provider(): array { + $extrasendpasswordchangeconfirmationemail = new \stdClass(); + $extrasendpasswordchangeconfirmationemail->token = '123'; + + return [ + ['send_password_change_info', null], + ['send_confirmation_email', null], + ['setnew_password_and_mail', false], + ['send_password_change_confirmation_email', $extrasendpasswordchangeconfirmationemail], + ]; + } } diff --git a/user/edit.php b/user/edit.php index 0524632a7c975..0f1e0f94223fd 100644 --- a/user/edit.php +++ b/user/edit.php @@ -263,7 +263,12 @@ $a = new stdClass(); $a->url = $CFG->wwwroot . '/user/emailupdate.php?key=' . $emailchangedkey . '&id=' . $user->id; $a->site = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID))); - $a->fullname = fullname($tempuser, true); + + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $a->{$field} = $value; + } + $a->supportemail = $OUTPUT->supportemail(); $emailupdatemessage = get_string('emailupdatemessage', 'auth', $a); diff --git a/user/tests/userlib_test.php b/user/tests/userlib_test.php index 0cf16b679f722..181f19d1a8980 100644 --- a/user/tests/userlib_test.php +++ b/user/tests/userlib_test.php @@ -1048,4 +1048,39 @@ public function test_user_get_user_details_groups(): void { $this->assertCount(1, $result['groups']); $this->assertEquals($group2->id, $result['groups'][0]['id']); } + + /** + * Verifies that the get_name_placeholders function correctly generates + * an array of name placeholders for a given user object. + * + * @covers ::get_name_placeholders() + */ + public function test_get_name_placeholders(): void { + $this->resetAfterTest(); + + // Set format for fullname and alternativefullname. + set_config('fullnamedisplay', 'firstname, lastname'); + set_config('alternativefullnameformat', 'firstnamephonetic lastnamephonetic'); + + // Create the target user. + $user = $this->getDataGenerator()->create_user([ + 'firstname' => 'FN', + 'lastname' => 'LN', + 'firstnamephonetic' => 'FNP', + 'lastnamephonetic' => 'LNP', + 'middlename' => 'MN', + 'alternatename' => 'AN', + ]); + + // Add user name fields to $a as an object based on $user. + $a = new \stdClass(); + $placeholders = \core_user::get_name_placeholders($user); + foreach ($placeholders as $field => $value) { + $a->{$field} = $value; + } + $this->assertEquals("Hello $a->firstname", "Hello FN"); + $this->assertEquals("Bonjour $a->fullname", "Bonjour FN, LN"); + $this->assertEquals("Privyet $a->alternativefullname", "Privyet FNP LNP"); + $this->assertEquals("Hola $a->alternatename '$a->middlename' $a->lastname", "Hola AN 'MN' LN"); + } }