From 752a75008a3361e928807dbdc160ffe8b128f39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ure=C3=B1a?= Date: Fri, 5 Sep 2025 15:44:41 +0200 Subject: [PATCH] MDL-84960 core: Added initials to the user structures --- .upgradenotes/MDL-84960-2025091612160080.yml | 30 +++ public/enrol/externallib.php | 49 ++++- public/enrol/tests/externallib_test.php | 180 +++++++++++++++++- .../get_enrolled_users_for_selector.php | 2 + .../classes/external/get_gradable_users.php | 2 + .../classes/external/get_users_in_report.php | 2 + .../external/get_users_in_report_test.php | 85 +++++++++ .../get_enrolled_users_for_selector_test.php | 102 ++++++++++ .../external/get_gradable_users_test.php | 13 ++ public/message/classes/api.php | 1 + public/message/classes/helper.php | 2 + public/message/externallib.php | 3 + public/message/tests/externallib_test.php | 21 ++ public/mod/assign/externallib.php | 11 +- .../forum/classes/local/entities/author.php | 15 +- .../forum/classes/local/exporters/author.php | 9 + .../forum/classes/local/factories/entity.php | 5 +- public/mod/forum/externallib.php | 6 + public/mod/forum/tests/externallib_test.php | 13 +- public/user/externallib.php | 4 + public/user/tests/externallib_test.php | 5 + 21 files changed, 545 insertions(+), 15 deletions(-) create mode 100644 .upgradenotes/MDL-84960-2025091612160080.yml create mode 100644 public/grade/report/grader/tests/external/get_users_in_report_test.php create mode 100644 public/grade/tests/external/get_enrolled_users_for_selector_test.php diff --git a/.upgradenotes/MDL-84960-2025091612160080.yml b/.upgradenotes/MDL-84960-2025091612160080.yml new file mode 100644 index 00000000000..d991dddf427 --- /dev/null +++ b/.upgradenotes/MDL-84960-2025091612160080.yml @@ -0,0 +1,30 @@ +issueNumber: MDL-84960 +notes: + core: + - message: >- + Several core web services now include a new initials field in user data + structures. This change is backward-compatible and only adds an optional + field; no existing fields or field semantics have been changed. + Client applications should ensure they can handle the additional initials + field in web service responses, but clients that ignore unknown fields can + continue working without changes. The affected web services are: + - core_enrol_get_enrolled_users + - core_enrol_get_enrolled_users_with_capability + - core_enrol_get_potential_users + - core_enrol_search_users + - core_user_get_users_by_field + - core_user_get_users + - core_user_get_course_user_profiles + - core_grades_get_enrolled_users_for_selector + - core_grades_get_gradable_users + - gradereport_grader_get_users_in_report + - core_message_get_contact_requests + - core_message_get_conversation_members + - core_message_message_search_users + - core_message_get_user_contacts + - core_message_get_member_info + - core_message_get_conversation_messages + - mod_assign_list_participants + - mod_assign_get_participant + - mod_forum_get_forum_discussions + type: improved diff --git a/public/enrol/externallib.php b/public/enrol/externallib.php index 08a63064326..661fbb71707 100644 --- a/public/enrol/externallib.php +++ b/public/enrol/externallib.php @@ -103,6 +103,7 @@ class core_enrol_external extends external_api { $groupid = 0; $onlyactive = false; $userfields = array(); + $includeinitials = false; $limitfrom = 0; $limitnumber = 0; foreach ($params['options'] as $option) { @@ -115,8 +116,12 @@ class core_enrol_external extends external_api { break; case 'userfields': $thefields = explode(',', $option['value']); - foreach ($thefields as $f) { - $userfields[] = clean_param($f, PARAM_ALPHANUMEXT); + foreach ($thefields as $field) { + $clean = clean_param($field, PARAM_ALPHANUMEXT); + if ($clean === '') { + continue; + } + $userfields[] = $clean; } break; case 'limitfrom' : @@ -163,6 +168,17 @@ class core_enrol_external extends external_api { // To see the permissions of others role:review capability is required. require_capability('moodle/role:review', $coursecontext); + // Decide when to include initials. + if (empty($userfields)) { + $includeinitials = true; + } else { + $key = array_search('initials', $userfields, true); + if ($key !== false) { + unset($userfields[$key]); + $includeinitials = true; + } + } + foreach ($coursecapability['capabilities'] as $capability) { $courseusers['courseid'] = $courseid; $courseusers['capability'] = $capability; @@ -180,6 +196,9 @@ class core_enrol_external extends external_api { $users = array(); foreach ($enrolledusers as $courseuser) { if ($userdetails = user_get_user_details($courseuser, $course, $userfields)) { + if ($includeinitials) { + $userdetails['initials'] = core_user::get_initials($courseuser); + } $users[] = $userdetails; } } @@ -209,6 +228,7 @@ class core_enrol_external extends external_api { 'username' => new external_value(PARAM_RAW, 'Username', VALUE_OPTIONAL), 'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL), 'lastname' => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL), + 'initials' => new external_value(PARAM_NOTAGS, 'The initials of the user', VALUE_OPTIONAL), 'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'), 'email' => new external_value(PARAM_TEXT, 'Email address', VALUE_OPTIONAL), 'address' => new external_value(PARAM_TEXT, 'Postal address', VALUE_OPTIONAL), @@ -599,6 +619,7 @@ class core_enrol_external extends external_api { } $userdetails['customfields'] = array_values($userdetails['customfields']); } + $userdetails['initials'] = core_user::get_initials($user); $results[] = $userdetails; } } @@ -699,6 +720,7 @@ class core_enrol_external extends external_api { ); foreach ($users['users'] as $user) { if ($userdetails = user_get_user_details($user, $course, $requiredfields)) { + $userdetails['initials'] = core_user::get_initials($user); $results[] = $userdetails; } } @@ -782,6 +804,7 @@ class core_enrol_external extends external_api { $onlyactive = false; $onlysuspended = false; $userfields = []; + $includeinitials = false; $limitfrom = 0; $limitnumber = 0; $sortby = 'us.id'; @@ -803,8 +826,12 @@ class core_enrol_external extends external_api { break; case 'userfields': $thefields = explode(',', $option['value']); - foreach ($thefields as $f) { - $userfields[] = clean_param($f, PARAM_ALPHANUMEXT); + foreach ($thefields as $field) { + $clean = clean_param($field, PARAM_ALPHANUMEXT); + if ($clean === '') { + continue; + } + $userfields[] = $clean; } break; case 'limitfrom' : @@ -853,6 +880,16 @@ class core_enrol_external extends external_api { } course_require_view_participants($context); + // Decide when to include initials. + if (empty($userfields)) { + $includeinitials = true; + } else { + $key = array_search('initials', $userfields, true); + if ($key !== false) { + unset($userfields[$key]); + $includeinitials = true; + } + } // to overwrite this parameter, you need role:review capability if ($withcapability) { @@ -912,7 +949,8 @@ class core_enrol_external extends external_api { $userpicture = new user_picture($user); $userpicture->size = 1; $userdetails['profileimageurl'] = $userpicture->get_url($PAGE)->out(false); - } + } + $userdetails['initials'] = core_user::get_initials($user); $users[] = $userdetails; } $enrolledusers->close(); @@ -933,6 +971,7 @@ class core_enrol_external extends external_api { 'username' => new external_value(PARAM_RAW, 'Username policy is defined in Moodle security config', VALUE_OPTIONAL), 'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL), 'lastname' => new external_value(PARAM_NOTAGS, 'The family name of the user', VALUE_OPTIONAL), + 'initials' => new external_value(PARAM_NOTAGS, 'The initials of the user', VALUE_OPTIONAL), 'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'), 'email' => new external_value(PARAM_TEXT, 'An email address - allow email as root@localhost', VALUE_OPTIONAL), 'address' => new external_value(PARAM_TEXT, 'Postal address', VALUE_OPTIONAL), diff --git a/public/enrol/tests/externallib_test.php b/public/enrol/tests/externallib_test.php index fc3ab4b170c..f7014027a4c 100644 --- a/public/enrol/tests/externallib_test.php +++ b/public/enrol/tests/externallib_test.php @@ -21,6 +21,7 @@ use core_enrol_external; use core_external\external_api; use enrol_user_enrolment_form; use stdClass; +use core_user; defined('MOODLE_INTERNAL') || die(); @@ -37,6 +38,7 @@ require_once($CFG->dirroot . '/enrol/externallib.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.4 */ +#[\PHPUnit\Framework\Attributes\CoversClass(core_enrol_external::class)] final class externallib_test extends \core_external\tests\externallib_testcase { /** * dataProvider for test_get_enrolled_users_visibility(). @@ -301,7 +303,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id)); - // Create a role to add the allowedcaps. Users will have this role assigned. + // Create a role to add the allowed capabilities. Users will have this role assigned. $roleid = $this->getDataGenerator()->create_role(); // Allow the specified capabilities. if (!empty($settings['allowedcaps'])) { @@ -451,6 +453,105 @@ final class externallib_test extends \core_external\tests\externallib_testcase { core_enrol_external::get_enrolled_users($course->id, $options); } + /** + * Verify get_enrolled_users() initials behaviour with and without userfields. + */ + public function test_get_enrolled_users_initials(): void { + $this->resetAfterTest(); + + // Create the course and the users. + $course = $this->getDataGenerator()->create_course(); + $coursecontext = \context_course::instance($course->id); + $user0 = $this->getDataGenerator()->create_user(['username' => 'user0active']); + + // Create a role to add the allowedcaps. Users will have this role assigned. + $roleid = $this->getDataGenerator()->create_role(); + // Allow the specified capabilities. + assign_capability('moodle/course:enrolreview', CAP_ALLOW, $roleid, $coursecontext); + assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $roleid, $coursecontext); + + // Enrol the user in the course. + $this->getDataGenerator()->enrol_user($user0->id, $course->id, $roleid); + + // Switch to the enrolled user so the external function has a valid course context. + $this->setUser($user0); + + // Case 1: No userfields requested => initials must be returned. + $options = [ + ['name' => 'onlyactive', 'value' => true], + ]; + $activeusers = core_enrol_external::get_enrolled_users($course->id, $options); + + // We need to execute the return values cleaning process to simulate the web service server. + $activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers); + $this->assertCount(1, $activeusers); + $this->assertArrayHasKey('initials', $activeusers[0]); + + // Case 2: userfields requested but "initials" not included => initials must NOT be returned. + $options = [ + ['name' => 'onlyactive', 'value' => true], + ['name' => 'userfields', 'value' => 'id,username'], + ]; + $activeusers = core_enrol_external::get_enrolled_users($course->id, $options); + $activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers); + $this->assertCount(1, $activeusers); + $this->assertArrayNotHasKey('initials', $activeusers[0]); + + // Case 3: userfields requested including "initials" => initials must be returned. + $options = [ + ['name' => 'onlyactive', 'value' => true], + ['name' => 'userfields', 'value' => 'id,username,initials'], + ]; + $activeusers = core_enrol_external::get_enrolled_users($course->id, $options); + $activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers); + $this->assertCount(1, $activeusers); + $this->assertArrayHasKey('initials', $activeusers[0]); + } + + /** + * Test initials behaviour using the default name fields (no userfields option). + */ + public function test_get_enrolled_users_initials_alternative_names(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $user1 = $this->getDataGenerator()->create_user([ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'middlename' => 'William', + ]); + $users[$user1->id] = $user1; + $user2 = $this->getDataGenerator()->create_user([ + 'firstname' => 'María', + 'lastname' => 'García', + 'firstnamephonetic' => 'Maria', + 'lastnamephonetic' => 'Garcia', + ]); + $users[$user2->id] = $user2; + $user3 = $this->getDataGenerator()->create_user([ + 'firstname' => 'Robert', + 'lastname' => 'Johnson', + 'alternatename' => 'Bob', + ]); + $users[$user3->id] = $user3; + + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + $this->getDataGenerator()->enrol_user($user3->id, $course->id); + + $result = core_enrol_external::get_enrolled_users($course->id); + // We need to execute the return values cleaning process to simulate the web service server. + $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $result); + + // Default format (firstname lastname): initials must match core_user::get_initials(). + foreach ($result as $user) { + $this->assertNotEmpty($user['initials']); + $expectedinitials = core_user::get_initials($users[$user['id']]); + $this->assertEquals($expectedinitials, $user['initials']); + } + } + /** * Test get_users_courses */ @@ -1067,7 +1168,80 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $expecteduserlist = $result[0]['users']; $expecteduser = reset($expecteduserlist); $this->assertEquals(1, count($expecteduserlist)); + // The returned user must contain a valid user structure. + $this->assertArrayHasKey('id', $expecteduser); $this->assertEquals($data->student1->id, $expecteduser['id']); + // With userfields restricted to id, initials should not be present. + $this->assertArrayNotHasKey('initials', $expecteduser); + } + + /** + * Test get_enrolled_users_with_capability initials behaviour with and without userfields. + */ + public function test_get_enrolled_users_with_capability_initials(): void { + $capability = 'moodle/course:viewparticipants'; + $data = $this->get_enrolled_users_with_capability_setup($capability); + + // Case 1: No userfields requested => initials must be returned. + $result = core_enrol_external::get_enrolled_users_with_capability( + [ + 'coursecapabilities' => [ + 'courseid' => $data->course->id, + 'capabilities' => [ + $capability, + ], + ], + ], + [] + ); + $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result); + $expecteduserlist = $result[0]['users']; + $this->assertEquals(2, count($expecteduserlist)); + foreach ($expecteduserlist as $user) { + $this->assertArrayHasKey('initials', $user); + } + + // Case 2: userfields requested but "initials" not included => initials must NOT be returned. + $result = core_enrol_external::get_enrolled_users_with_capability( + [ + 'coursecapabilities' => [ + 'courseid' => $data->course->id, + 'capabilities' => [ + $capability, + ], + ], + ], + [ + ['name' => 'userfields', 'value' => 'id,firstname'], + ] + ); + $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result); + $expecteduserlist = $result[0]['users']; + $this->assertEquals(2, count($expecteduserlist)); + foreach ($expecteduserlist as $user) { + $this->assertArrayNotHasKey('initials', $user); + } + + // Case 3: userfields requested including "initials" => initials must be returned. + $result = core_enrol_external::get_enrolled_users_with_capability( + [ + 'coursecapabilities' => [ + 'courseid' => $data->course->id, + 'capabilities' => [ + $capability, + ], + ], + ], + [ + ['name' => 'userfields', 'value' => 'id,firstname,initials'], + ] + ); + $result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result); + $expecteduserlist = $result[0]['users']; + $this->assertEquals(2, count($expecteduserlist)); + foreach ($expecteduserlist as $user) { + $this->assertArrayHasKey('initials', $user); + } } /** @@ -1404,6 +1578,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user1->id, $result['id']); $this->assertEquals($user1->email, $result['email']); $this->assertEquals(fullname($user1), $result['fullname']); + $this->assertEquals(core_user::get_initials($user1), $result['initials']); $this->setUser($user1); @@ -1529,8 +1704,9 @@ final class externallib_test extends \core_external\tests\externallib_testcase { // Check the fields are the expected ones. $this->assertEquals(['id', 'fullname', 'customfields', - 'profileimageurl', 'profileimageurlsmall', 'department'], array_keys($result1)); + 'profileimageurl', 'profileimageurlsmall', 'department', 'initials'], array_keys($result1)); $this->assertEquals('Eigh User', $result1['fullname']); + $this->assertEquals(core_user::get_initials($user1), $result1['initials']); $this->assertEquals('Amphibians', $result1['department']); // Check the custom fields ONLY include the user identity one. diff --git a/public/grade/classes/external/get_enrolled_users_for_selector.php b/public/grade/classes/external/get_enrolled_users_for_selector.php index 5c7bf55b471..b2631c46d3d 100644 --- a/public/grade/classes/external/get_enrolled_users_for_selector.php +++ b/public/grade/classes/external/get_enrolled_users_for_selector.php @@ -16,6 +16,7 @@ namespace core_grades\external; +use core_user; use core_user_external; use core_external\external_api; use core_external\external_function_parameters; @@ -102,6 +103,7 @@ class get_enrolled_users_for_selector extends external_api { $userforselector = new \stdClass(); $userforselector->id = $userdata->user->id; $userforselector->fullname = fullname($userdata->user); + $userforselector->initials = core_user::get_initials($userdata->user); foreach (\core_user\fields::get_name_fields() as $field) { $userforselector->$field = $userdata->user->$field ?? null; } diff --git a/public/grade/classes/external/get_gradable_users.php b/public/grade/classes/external/get_gradable_users.php index a6efdc9bb75..c2d3b60a4c7 100644 --- a/public/grade/classes/external/get_gradable_users.php +++ b/public/grade/classes/external/get_gradable_users.php @@ -24,6 +24,7 @@ use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use core_external\restricted_context_exception; +use core_user; use core_user_external; use invalid_parameter_exception; use moodle_exception; @@ -94,6 +95,7 @@ class get_gradable_users extends external_api { $users = get_gradable_users($course->id, $params['groupid'], $onlyactive); $users = array_map(function ($user) use ($PAGE) { $user->fullname = fullname($user); + $user->initials = core_user::get_initials($user); $userpicture = new user_picture($user); $userpicture->size = 1; $user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false); diff --git a/public/grade/report/grader/classes/external/get_users_in_report.php b/public/grade/report/grader/classes/external/get_users_in_report.php index eaeca1d65b9..1f332da8aae 100644 --- a/public/grade/report/grader/classes/external/get_users_in_report.php +++ b/public/grade/report/grader/classes/external/get_users_in_report.php @@ -17,6 +17,7 @@ namespace gradereport_grader\external; use context_course; +use core_user; use core_user_external; use core_external\external_api; use core_external\external_function_parameters; @@ -95,6 +96,7 @@ class get_users_in_report extends external_api { $userforselector = new \stdClass(); $userforselector->id = $user->id; $userforselector->fullname = fullname($user); + $userforselector->initials = core_user::get_initials($user); foreach (\core_user\fields::get_name_fields() as $field) { $userforselector->$field = $user->$field ?? null; } diff --git a/public/grade/report/grader/tests/external/get_users_in_report_test.php b/public/grade/report/grader/tests/external/get_users_in_report_test.php new file mode 100644 index 00000000000..16b7d401f18 --- /dev/null +++ b/public/grade/report/grader/tests/external/get_users_in_report_test.php @@ -0,0 +1,85 @@ +. + +/** + * Unit tests for the gradereport_grader\external\get_users_in_report. + * + * @package gradereport_grader + * @category external + * @copyright 2025 Daniel Ureña + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace gradereport_grader\external; + +use gradereport_grader\external\get_users_in_report; +use core_external\external_api; +/** + * Unit tests for the gradereport_grader\external\get_users_in_report. + * + * @package gradereport_grader + * @category external + * @copyright 2025 Daniel Ureña + */ +#[\PHPUnit\Framework\Attributes\CoversClass(get_users_in_report::class)] +final class get_users_in_report_test extends \core_external\tests\externallib_testcase { + public function test_execute(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create course and users. + $course = $this->getDataGenerator()->create_course(); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + // Enrol users in course. + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + + // Call the external function. + $result = get_users_in_report::execute($course->id); + $result = external_api::clean_returnvalue(get_users_in_report::execute_returns(), $result); + + // Two enrolled users should be returned. + $this->assertCount(2, $result['users']); + + $createdusers = [ + $user1->id => $user1, + $user2->id => $user2, + ]; + + // Assert required fields and that their values match the created users. + foreach ($result['users'] as $user) { + $this->assertArrayHasKey('id', $user); + $this->assertArrayHasKey('fullname', $user); + $this->assertArrayHasKey('initials', $user); + $this->assertArrayHasKey('profileimageurl', $user); + + // It must be one of the enrolled users. + $this->assertArrayHasKey($user['id'], $createdusers); + $expecteduser = $createdusers[$user['id']]; + + $this->assertEquals(fullname($expecteduser), $user['fullname']); + $this->assertEquals(\core_user::get_initials($expecteduser), $user['initials']); + + // Profile image should be a non-empty string. + $this->assertIsString($user['profileimageurl']); + $this->assertNotEmpty($user['profileimageurl']); + } + + // Assert no warnings. + $this->assertEmpty($result['warnings']); + } +} diff --git a/public/grade/tests/external/get_enrolled_users_for_selector_test.php b/public/grade/tests/external/get_enrolled_users_for_selector_test.php new file mode 100644 index 00000000000..d6da286ce0d --- /dev/null +++ b/public/grade/tests/external/get_enrolled_users_for_selector_test.php @@ -0,0 +1,102 @@ +. + +/** + * Unit tests for the core_grades\external\get_enrolled_users_for_selector. + * + * @package core_grades + * @category external + * @copyright 2025 Daniel Ureña + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_grades\external; + +use core_grades\external\get_enrolled_users_for_selector; +use core_external\external_api; +use core_user; + +/** + * Unit tests for the core_grades\external\get_enrolled_users_for_selector. + * + * @package core_grades + * @category external + * @copyright 2025 Daniel Ureña + */ +#[\PHPUnit\Framework\Attributes\CoversClass(get_enrolled_users_for_selector::class)] +final class get_enrolled_users_for_selector_test extends \core_external\tests\externallib_testcase { + public function test_get_enrolled_users_for_selector(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create course and users. + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + // Create an activity to ensure grade items exist. + $assign = $generator->create_module('assign', ['course' => $course->id]); + $user1 = $generator->create_user([ + 'firstname' => 'Ana', + 'lastname' => 'García', + ]); + $user2 = $generator->create_user([ + 'firstname' => 'Luis', + 'lastname' => 'Martínez', + ]); + + $createdusers = [ + $user1->id => $user1, + $user2->id => $user2, + ]; + + // Enrol users in course. + $generator->enrol_user($user1->id, $course->id); + $generator->enrol_user($user2->id, $course->id); + + // Call the external function. + $result = get_enrolled_users_for_selector::execute($course->id, 0); + $result = external_api::clean_returnvalue(get_enrolled_users_for_selector::execute_returns(), $result); + + // Assert users are returned. + $this->assertCount(2, $result['users']); + + // Assert required fields and that their values match the created users. + foreach ($result['users'] as $user) { + $this->assertArrayHasKey('id', $user); + $this->assertArrayHasKey('fullname', $user); + $this->assertArrayHasKey('initials', $user); + $this->assertArrayHasKey('profileimageurl', $user); + $this->assertArrayHasKey('profileimageurlsmall', $user); + + // It must be one of the enrolled users. + $this->assertArrayHasKey($user['id'], $createdusers); + $expecteduser = $createdusers[$user['id']]; + + $this->assertEquals(fullname($expecteduser), $user['fullname']); + $this->assertEquals(core_user::get_initials($expecteduser), $user['initials']); + $this->assertEquals($expecteduser->firstname, $user['firstname']); + $this->assertEquals($expecteduser->lastname, $user['lastname']); + + // Profile images should be non-empty strings. + $this->assertIsString($user['profileimageurl']); + $this->assertNotEmpty($user['profileimageurl']); + $this->assertIsString($user['profileimageurlsmall']); + $this->assertNotEmpty($user['profileimageurlsmall']); + } + + // Assert no warnings. + $this->assertEmpty($result['warnings']); + } +} diff --git a/public/grade/tests/external/get_gradable_users_test.php b/public/grade/tests/external/get_gradable_users_test.php index 772a4e84e91..9110e027793 100644 --- a/public/grade/tests/external/get_gradable_users_test.php +++ b/public/grade/tests/external/get_gradable_users_test.php @@ -80,6 +80,7 @@ final class get_gradable_users_test extends \core_external\tests\externallib_tes $mapped = array_map(function($user) { return [ 'fullname' => $user['fullname'], + 'initials' => $user['initials'], 'firstname' => $user['firstname'], 'lastname' => $user['lastname'], 'profileimageurl' => $user['profileimageurl'], @@ -104,30 +105,35 @@ final class get_gradable_users_test extends \core_external\tests\externallib_tes 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', + 'initials' => 'AA', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', + 'initials' => 'BB', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', + 'initials' => 'CC', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon', 'fullname' => 'Durian Dracontomelon', + 'initials' => 'DD', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Eggplant', 'lastname' => 'Ensete', 'fullname' => 'Eggplant Ensete', + 'initials' => 'EE', ], ], ], @@ -140,24 +146,28 @@ final class get_gradable_users_test extends \core_external\tests\externallib_tes 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', + 'initials' => 'AA', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', + 'initials' => 'BB', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', + 'initials' => 'CC', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon', 'fullname' => 'Durian Dracontomelon', + 'initials' => 'DD', ], ], ], @@ -170,18 +180,21 @@ final class get_gradable_users_test extends \core_external\tests\externallib_tes 'firstname' => 'Apple', 'lastname' => 'Apricot', 'fullname' => 'Apple Apricot', + 'initials' => 'AA', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Banana', 'lastname' => 'Blueberry', 'fullname' => 'Banana Blueberry', + 'initials' => 'BB', ], [ 'profileimageurl' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', 'firstname' => 'Cherry', 'lastname' => 'Cranberry', 'fullname' => 'Cherry Cranberry', + 'initials' => 'CC', ], ], ], diff --git a/public/message/classes/api.php b/public/message/classes/api.php index 560cbf19842..e019d0ec40f 100644 --- a/public/message/classes/api.php +++ b/public/message/classes/api.php @@ -329,6 +329,7 @@ class api { // Otherwise it means that the $USER was not allowed to search the returned user. if (!empty($userdetails) and !empty($userdetails['fullname'])) { // We know we've matched, but only save the record if it's within the offset area we need. + $user->initials = \core_user::get_initials($user); if ($limitfrom == 0) { // No offset specified, so just save. $returnedusers[$id] = $user; diff --git a/public/message/classes/helper.php b/public/message/classes/helper.php index c7ca0754113..7e2fe00a7b9 100644 --- a/public/message/classes/helper.php +++ b/public/message/classes/helper.php @@ -166,6 +166,7 @@ class helper { $data->userid = $userfields->id; $data->useridfrom = null; $data->fullname = fullname($userfields); + $data->initials = \core_user::get_initials($userfields); // Get the user picture data. $userpicture = new \user_picture($userfields); $userpicture->size = 1; // Size f1. @@ -437,6 +438,7 @@ class helper { $data = new stdClass(); $data->id = $member->id; $data->fullname = fullname($member); + $data->initials = \core_user::get_initials($member); // Create the URL for their profile. $profileurl = new \moodle_url('/user/profile.php', ['id' => $member->id]); diff --git a/public/message/externallib.php b/public/message/externallib.php index c4cd6dda57a..22630e6e8b7 100644 --- a/public/message/externallib.php +++ b/public/message/externallib.php @@ -997,6 +997,7 @@ class core_message_external extends external_api { array( 'userid' => new external_value(PARAM_INT, 'The user\'s id'), 'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'), + 'initials' => new external_value(PARAM_NOTAGS, 'The initials of the user', VALUE_OPTIONAL), 'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'), 'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'), 'ismessaging' => new external_value(PARAM_BOOL, 'If we are messaging the user'), @@ -1059,6 +1060,7 @@ class core_message_external extends external_api { $result = [ 'id' => new external_value(PARAM_INT, 'The user id'), 'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'), + 'initials' => new external_value(PARAM_NOTAGS, 'The initials of the user', VALUE_OPTIONAL), 'profileurl' => new external_value(PARAM_URL, 'The link to the user\'s profile page'), 'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'), 'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'), @@ -1256,6 +1258,7 @@ class core_message_external extends external_api { $contact = new \stdClass(); $contact->userid = $message->userid; $contact->fullname = $message->fullname; + $contact->initials = $message->initials; $contact->profileimageurl = $message->profileimageurl; $contact->profileimageurlsmall = $message->profileimageurlsmall; $contact->messageid = $message->messageid; diff --git a/public/message/tests/externallib_test.php b/public/message/tests/externallib_test.php index 80c0561a558..597e2aeeb49 100644 --- a/public/message/tests/externallib_test.php +++ b/public/message/tests/externallib_test.php @@ -19,6 +19,7 @@ namespace core_message; use core_external\external_api; use core_message\tests\helper as testhelper; use core_message_external; +use core_user; defined('MOODLE_INTERNAL') || die(); @@ -451,6 +452,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user2->id, $request['id']); $this->assertEquals(fullname($user2), $request['fullname']); + $this->assertEquals(core_user::get_initials($user2), $request['initials']); $this->assertArrayHasKey('profileimageurl', $request); $this->assertArrayHasKey('profileimageurlsmall', $request); $this->assertArrayHasKey('isonline', $request); @@ -2186,7 +2188,9 @@ final class externallib_test extends \core_external\tests\externallib_testcase { // Check that we retrieved the correct contacts. $this->assertCount(2, $contacts); $this->assertEquals($users[2]->id, $contacts[0]['id']); + $this->assertEquals(core_user::get_initials($users[2]), $contacts[0]['initials']); $this->assertEquals($users[3]->id, $contacts[1]['id']); + $this->assertEquals(core_user::get_initials($users[3]), $contacts[1]['initials']); // Verify the correct conversations were returned for the contacts. $this->assertCount(2, $contacts[0]['conversations']); @@ -2204,9 +2208,13 @@ final class externallib_test extends \core_external\tests\externallib_testcase { // In this case, as a student, that's the course contact for course2 and those noncontacts sharing a course with user1. $this->assertCount(4, $noncontacts); $this->assertEquals($users[1]->id, $noncontacts[0]['id']); + $this->assertEquals(core_user::get_initials($users[1]), $noncontacts[0]['initials']); $this->assertEquals($users[6]->id, $noncontacts[1]['id']); + $this->assertEquals(core_user::get_initials($users[6]), $noncontacts[1]['initials']); $this->assertEquals($users[7]->id, $noncontacts[2]['id']); + $this->assertEquals(core_user::get_initials($users[7]), $noncontacts[2]['initials']); $this->assertEquals($users[9]->id, $noncontacts[3]['id']); + $this->assertEquals(core_user::get_initials($users[9]), $noncontacts[3]['initials']); // Verify the correct conversations were returned for the non-contacts. $this->assertCount(1, $noncontacts[1]['conversations']); @@ -2483,6 +2491,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user2->id, $message1['userid']); $this->assertEquals(fullname($user2), $message1['fullname']); + $this->assertEquals(core_user::get_initials($user2), $message1['initials']); $this->assertTrue($message1['ismessaging']); $this->assertFalse($message1['sentfromcurrentuser']); $this->assertEquals('Word.', $message1['lastmessage']); @@ -2495,6 +2504,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user2->id, $message2['userid']); $this->assertEquals(fullname($user2), $message2['fullname']); + $this->assertEquals(core_user::get_initials($user2), $message2['initials']); $this->assertTrue($message2['ismessaging']); $this->assertTrue($message2['sentfromcurrentuser']); $this->assertEquals('Yo!', $message2['lastmessage']); @@ -2658,14 +2668,17 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user2->id, $contact1['id']); $this->assertEquals(fullname($user2), $contact1['fullname']); + $this->assertEquals(core_user::get_initials($user2), $contact1['initials']); $this->assertTrue($contact1['iscontact']); $this->assertEquals($user3->id, $contact2['id']); $this->assertEquals(fullname($user3), $contact2['fullname']); + $this->assertEquals(core_user::get_initials($user3), $contact2['initials']); $this->assertTrue($contact2['iscontact']); $this->assertEquals($user4->id, $contact3['id']); $this->assertEquals(fullname($user4), $contact3['fullname']); + $this->assertEquals(core_user::get_initials($user4), $contact3['initials']); $this->assertTrue($contact3['iscontact']); } @@ -2842,6 +2855,11 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertContainsEquals(fullname($user2), $membersfullnames); $this->assertContainsEquals(fullname($user3), $membersfullnames); + $membersinitials = [$members[0]['initials'], $members[1]['initials'], $members[2]['initials']]; + $this->assertContainsEquals(core_user::get_initials($user1), $membersinitials); + $this->assertContainsEquals(core_user::get_initials($user2), $membersinitials); + $this->assertContainsEquals(core_user::get_initials($user3), $membersinitials); + // Confirm the messages data is correct. $messages = $result['messages']; $this->assertCount(4, $messages); @@ -4521,6 +4539,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { // Confirm the standard fields are OK. $this->assertEquals($user1->id, $member1->id); $this->assertEquals(fullname($user1), $member1->fullname); + $this->assertEquals(core_user::get_initials($user1), $member1->initials); $this->assertEquals(true, $member1->isonline); $this->assertEquals(true, $member1->showonlinestatus); $this->assertEquals(false, $member1->iscontact); @@ -4530,6 +4549,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user2->id, $member2->id); $this->assertEquals(fullname($user2), $member2->fullname); + $this->assertEquals(core_user::get_initials($user2), $member2->initials); $this->assertEquals(false, $member2->isonline); $this->assertEquals(true, $member2->showonlinestatus); $this->assertEquals(true, $member2->iscontact); @@ -4539,6 +4559,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($user3->id, $member3->id); $this->assertEquals(fullname($user3), $member3->fullname); + $this->assertEquals(core_user::get_initials($user3), $member3->initials); $this->assertEquals(false, $member3->isonline); $this->assertEquals(true, $member3->showonlinestatus); $this->assertEquals(false, $member3->iscontact); diff --git a/public/mod/assign/externallib.php b/public/mod/assign/externallib.php index 725e600ca52..ccc0067b735 100644 --- a/public/mod/assign/externallib.php +++ b/public/mod/assign/externallib.php @@ -2729,8 +2729,14 @@ class mod_assign_external extends \mod_assign\external\external_api { // Now we do the expensive lookup of user details because we completed the filtering. if (!$assign->is_blind_marking() && !$params['onlyids']) { $userdetails = user_get_user_details($record, $course, $userfields); + if ($userdetails === null) { + // If user details are not available (e.g. access prevented), fall back to minimal data. + $userdetails = ['id' => $record->id]; + } else { + $userdetails['initials'] = core_user::get_initials($record); + } } else { - $userdetails = array('id' => $record->id); + $userdetails = ['id' => $record->id]; } $userdetails['fullname'] = $fullname; $userdetails['submitted'] = $record->submitted; @@ -2769,7 +2775,7 @@ class mod_assign_external extends \mod_assign\external\external_api { $userdesc = core_user_external::user_description(); // List unneeded properties. $unneededproperties = [ - 'auth', 'confirmed', 'lang', 'calendartype', 'theme', 'timezone', 'mailformat' + 'auth', 'confirmed', 'lang', 'calendartype', 'theme', 'timezone', 'mailformat', ]; // Remove unneeded properties for consistency with the previous version. foreach ($unneededproperties as $prop) { @@ -2913,6 +2919,7 @@ class mod_assign_external extends \mod_assign\external\external_api { // hasn't asked for user details to be embedded. if (!$assign->is_blind_marking() && $embeduser) { if ($userdetails = user_get_user_details($participant, $course)) { + $userdetails['initials'] = core_user::get_initials($participant); $return['user'] = $userdetails; } } diff --git a/public/mod/forum/classes/local/entities/author.php b/public/mod/forum/classes/local/entities/author.php index 28e53a7a9fe..96f1124f632 100644 --- a/public/mod/forum/classes/local/entities/author.php +++ b/public/mod/forum/classes/local/entities/author.php @@ -57,6 +57,8 @@ class author { private $alternatename; /** @var string $imagealt Image alt */ private $imagealt; + /** @var string $initials Initials */ + private $initials; /** * Constructor. @@ -85,7 +87,8 @@ class author { ?string $firstnamephonetic = null, ?string $lastnamephonetic = null, ?string $alternatename = null, - ?string $imagealt = null + ?string $imagealt = null, + ?string $initials = null ) { $this->id = $id; $this->pictureitemid = $pictureitemid; @@ -99,6 +102,7 @@ class author { $this->lastnamephonetic = $lastnamephonetic; $this->alternatename = $alternatename; $this->imagealt = $imagealt; + $this->initials = $initials; } /** @@ -208,4 +212,13 @@ class author { public function get_image_alt(): ?string { return $this->imagealt; } + + /** + * Return the initials. + * + * @return string + */ + public function get_initials(): string { + return $this->initials ?? ''; + } } diff --git a/public/mod/forum/classes/local/exporters/author.php b/public/mod/forum/classes/local/exporters/author.php index 17a3ceb8d5b..93653443a4a 100644 --- a/public/mod/forum/classes/local/exporters/author.php +++ b/public/mod/forum/classes/local/exporters/author.php @@ -91,6 +91,12 @@ class author extends exporter { 'default' => null, 'null' => NULL_ALLOWED ], + 'initials' => [ + 'type' => PARAM_NOTAGS, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED, + ], 'isdeleted' => [ 'type' => PARAM_BOOL, 'optional' => true, @@ -154,6 +160,7 @@ class author extends exporter { return [ 'id' => $author->get_id(), 'fullname' => get_string('deleteduser', 'mod_forum'), + 'initials' => null, 'isdeleted' => true, 'groups' => [], 'urls' => [ @@ -184,6 +191,7 @@ class author extends exporter { return [ 'id' => $author->get_id(), 'fullname' => $author->get_full_name(), + 'initials' => $author->get_initials(), 'isdeleted' => false, 'groups' => $groups, 'urls' => [ @@ -197,6 +205,7 @@ class author extends exporter { return [ 'id' => null, 'fullname' => get_string('forumauthorhidden', 'mod_forum'), + 'initials' => null, 'isdeleted' => null, 'groups' => [], 'urls' => [ diff --git a/public/mod/forum/classes/local/factories/entity.php b/public/mod/forum/classes/local/factories/entity.php index 100af4945a0..5bf5cd930a7 100644 --- a/public/mod/forum/classes/local/factories/entity.php +++ b/public/mod/forum/classes/local/factories/entity.php @@ -38,6 +38,7 @@ use context; use cm_info; use user_picture; use moodle_url; +use core_user; /** * Entity factory to create the forum entities. @@ -170,6 +171,7 @@ class entity { * @return author_entity */ public function get_author_from_stdclass(stdClass $record): author_entity { + $initials = core_user::get_initials($record); return new author_entity( $record->id, $record->picture, @@ -182,7 +184,8 @@ class entity { $record->firstnamephonetic, $record->lastnamephonetic, $record->alternatename, - $record->imagealt + $record->imagealt, + $initials, ); } diff --git a/public/mod/forum/externallib.php b/public/mod/forum/externallib.php index a4a8f4090eb..00e829881e8 100644 --- a/public/mod/forum/externallib.php +++ b/public/mod/forum/externallib.php @@ -506,18 +506,22 @@ class mod_forum_external extends external_api { if (forum_is_author_hidden($discussionobject, $forumrecord)) { $discussionobject->userid = null; $discussionobject->userfullname = null; + $discussionobject->userinitials = null; $discussionobject->userpictureurl = null; $discussionobject->usermodified = null; $discussionobject->usermodifiedfullname = null; + $discussionobject->usermodifiedinitials = null; $discussionobject->usermodifiedpictureurl = null; } else { $discussionobject->userfullname = $firstpostauthor->get_full_name(); + $discussionobject->userinitials = $firstpostauthor->get_initials(); $discussionobject->userpictureurl = $urlfactory->get_author_profile_image_url($firstpostauthor, null, 2) ->out(false); $discussionobject->usermodifiedfullname = $latestpostauthor->get_full_name(); + $discussionobject->usermodifiedinitials = $latestpostauthor->get_initials(); $discussionobject->usermodifiedpictureurl = $urlfactory->get_author_profile_image_url( $latestpostauthor, null, 2)->out(false); } @@ -567,7 +571,9 @@ class mod_forum_external extends external_api { 'totalscore' => new external_value(PARAM_INT, 'The post message total score'), 'mailnow' => new external_value(PARAM_INT, 'Mail now?'), 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'), + 'userinitials' => new external_value(PARAM_NOTAGS, 'Post author initials', VALUE_OPTIONAL), 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'), + 'usermodifiedinitials' => new external_value(PARAM_NOTAGS, 'Post modifier initials', VALUE_OPTIONAL), 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'), 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'), 'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'), diff --git a/public/mod/forum/tests/externallib_test.php b/public/mod/forum/tests/externallib_test.php index 19962aade9a..199eb84e113 100644 --- a/public/mod/forum/tests/externallib_test.php +++ b/public/mod/forum/tests/externallib_test.php @@ -17,6 +17,7 @@ namespace mod_forum; use core_external\external_api; +use core_user; use mod_forum_external; /** @@ -355,6 +356,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $exporteduser2 = [ 'id' => (int) $user2->id, 'fullname' => fullname($user2), + 'initials' => \core_user::get_initials($user2), 'isdeleted' => false, 'groups' => [], 'urls' => [ @@ -362,13 +364,13 @@ final class externallib_test extends \core_external\tests\externallib_testcase { 'profileimage' => $urlfactory->get_author_profile_image_url($user2entity), ] ]; - $user2->fullname = $exporteduser2['fullname']; $user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]); $user3entity = $entityfactory->get_author_from_stdClass($user3); $exporteduser3 = [ 'id' => (int) $user3->id, 'fullname' => fullname($user3), + 'initials' => \core_user::get_initials($user3), 'groups' => [], 'isdeleted' => false, 'urls' => [ @@ -376,7 +378,6 @@ final class externallib_test extends \core_external\tests\externallib_testcase { 'profileimage' => $urlfactory->get_author_profile_image_url($user3entity), ] ]; - $user3->fullname = $exporteduser3['fullname']; $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum'); // Set the first created user to the test user. @@ -453,6 +454,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $exporteduser3 = [ 'id' => (int) $user3->id, 'fullname' => get_string('deleteduser', 'mod_forum'), + 'initials' => '', 'groups' => [], 'isdeleted' => true, 'urls' => [ @@ -978,7 +980,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { 'message' => $post1->message, 'messageformat' => (int) $post1->messageformat, 'messagetrust' => (int) $post1->messagetrust, - 'attachment' => $post1->attachment, + 'attachment' => !empty($post1->attachment) ? $post1->attachment : false, 'totalscore' => (int) $post1->totalscore, 'mailnow' => (int) $post1->mailnow, 'userfullname' => fullname($user1), @@ -994,7 +996,6 @@ final class externallib_test extends \core_external\tests\externallib_testcase { 'starred' => false, 'canfavourite' => true ); - // Call the external function passing forum id. $discussions = mod_forum_external::get_forum_discussions($forum1->id); $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); @@ -1007,10 +1008,12 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $userpicture = new \user_picture($user1); $userpicture->size = 2; // Size f2. $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false); + $expectedreturn['discussions'][0]['userinitials'] = core_user::get_initials($user1); $userpicture = new \user_picture($user4); $userpicture->size = 2; // Size f2. $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false); + $expectedreturn['discussions'][0]['usermodifiedinitials'] = core_user::get_initials($user4); $this->assertEquals($expectedreturn, $discussions); @@ -2335,6 +2338,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $exporteduser1 = [ 'id' => (int) $user1->id, 'fullname' => fullname($user1), + 'initials' => \core_user::get_initials($user1), 'groups' => [], 'urls' => [ 'profile' => $urlfactory->get_author_profile_url($user1entity, $course1->id)->out(false), @@ -2348,6 +2352,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $exporteduser2 = [ 'id' => (int) $user2->id, 'fullname' => fullname($user2), + 'initials' => \core_user::get_initials($user2), 'groups' => [], 'urls' => [ 'profile' => $urlfactory->get_author_profile_url($user2entity, $course1->id)->out(false), diff --git a/public/user/externallib.php b/public/user/externallib.php index a4e820195d8..6259e7172ad 100644 --- a/public/user/externallib.php +++ b/public/user/externallib.php @@ -786,6 +786,7 @@ class core_user_external extends \core_external\external_api { // Return the user only if the searched field is returned. // Otherwise it means that the $USER was not allowed to search the returned user. if (!empty($userdetails) and !empty($userdetails[$field])) { + $userdetails['initials'] = core_user::get_initials($user); $returnedusers[] = $userdetails; } } @@ -948,6 +949,7 @@ class core_user_external extends \core_external\external_api { // Otherwise it means that the $USER was not allowed to search the returned user. if (!empty($userdetails)) { $validuser = true; + $userdetails['initials'] = core_user::get_initials($user); foreach ($params['criteria'] as $criteria) { if (empty($userdetails[$criteria['key']])) { @@ -1056,6 +1058,7 @@ class core_user_external extends \core_external\external_api { $context = context_course::instance($courseids[$user->id], IGNORE_MISSING); self::validate_context($context); if ($userarray = user_get_user_details($user, $course)) { + $userarray['initials'] = core_user::get_initials($user); $result[] = $userarray; } } @@ -1117,6 +1120,7 @@ class core_user_external extends \core_external\external_api { 'firstname' => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user', VALUE_OPTIONAL), 'lastname' => new external_value(core_user::get_property_type('lastname'), 'The family name of the user', VALUE_OPTIONAL), 'fullname' => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'), + 'initials' => new external_value(PARAM_NOTAGS, 'The initials of the user', VALUE_OPTIONAL), 'email' => new external_value(core_user::get_property_type('email'), 'An email address - allow email as root@localhost', VALUE_OPTIONAL), 'address' => new external_value(core_user::get_property_type('address'), 'Postal address', VALUE_OPTIONAL), 'phone1' => new external_value(core_user::get_property_type('phone1'), 'Phone 1', VALUE_OPTIONAL), diff --git a/public/user/tests/externallib_test.php b/public/user/tests/externallib_test.php index d97e8b2e190..2fa27c5d77c 100644 --- a/public/user/tests/externallib_test.php +++ b/public/user/tests/externallib_test.php @@ -29,6 +29,7 @@ namespace core_user; use core_external\external_api; use core_files_external; use core_user_external; +use core_user; defined('MOODLE_INTERNAL') || die(); @@ -120,6 +121,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { } $this->assertEquals($generateduser->firstname, $returneduser['firstname']); $this->assertEquals($generateduser->lastname, $returneduser['lastname']); + $this->assertEquals(core_user::get_initials($generateduser), $returneduser['initials']); if ($generateduser->email != $USER->email) { // Don't check the tmp modified $USER email. $this->assertEquals($generateduser->email, $returneduser['email']); } @@ -277,6 +279,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { } $this->assertEquals($generateduser->firstname, $returneduser['firstname']); $this->assertEquals($generateduser->lastname, $returneduser['lastname']); + $this->assertEquals(core_user::get_initials($generateduser), $returneduser['initials']); if ($generateduser->email != $USER->email) { //don't check the tmp modified $USER email $this->assertEquals($generateduser->email, $returneduser['email']); } @@ -436,6 +439,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { // Check we retrieve the good total number of enrolled users + no error on capability. $this->assertEquals(1, count($enrolledusers)); + $this->assertEquals(core_user::get_initials($USER), $enrolledusers[0]['initials']); } public function test_get_user_course_profile_as_admin(): void { @@ -466,6 +470,7 @@ final class externallib_test extends \core_external\tests\externallib_testcase { $this->assertEquals($data->user1->idnumber, $enrolleduser['idnumber']); $this->assertEquals($data->user1->firstname, $enrolleduser['firstname']); $this->assertEquals($data->user1->lastname, $enrolleduser['lastname']); + $this->assertEquals(core_user::get_initials($data->user1), $enrolleduser['initials']); $this->assertEquals($data->user1->email, $enrolleduser['email']); $this->assertEquals($data->user1->address, $enrolleduser['address']); $this->assertEquals($data->user1->phone1, $enrolleduser['phone1']);