diff --git a/lib/classes/user.php b/lib/classes/user.php index 0593334241d..98a03f080ff 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -1226,4 +1226,190 @@ class core_user { }; return null; } + + /** + * Return full name depending on context. + * This function should be used for displaying purposes only as the details may not be the same as it is on database. + * + * @param stdClass $user the person to get details of. + * @param context|null $context The context will be used to determine the visibility of the user's full name. + * @param array $options can include: override - if true, will not use forced firstname/lastname settings + * @return string Full name of the user + */ + public static function get_fullname(stdClass $user, context $context = null, array $options = []): string { + global $CFG, $SESSION; + + // Clone the user so that it does not mess up the original object. + $user = clone($user); + + // Override options. + $override = $options["override"] ?? false; + + if (!isset($user->firstname) && !isset($user->lastname)) { + return ''; + } + + // Get all of the name fields. + $allnames = \core_user\fields::get_name_fields(); + if ($CFG->debugdeveloper) { + $missingfields = []; + foreach ($allnames as $allname) { + if (!property_exists($user, $allname)) { + $missingfields[] = $allname; + } + } + if (!empty($missingfields)) { + debugging('The following name fields are missing from the user object: ' . implode(', ', $missingfields)); + } + } + + if (!$override) { + if (!empty($CFG->forcefirstname)) { + $user->firstname = $CFG->forcefirstname; + } + if (!empty($CFG->forcelastname)) { + $user->lastname = $CFG->forcelastname; + } + } + + if (!empty($SESSION->fullnamedisplay)) { + $CFG->fullnamedisplay = $SESSION->fullnamedisplay; + } + + $template = null; + // If the fullnamedisplay setting is available, set the template to that. + if (isset($CFG->fullnamedisplay)) { + $template = $CFG->fullnamedisplay; + } + // If the template is empty, or set to language, return the language string. + if ((empty($template) || $template == 'language') && !$override) { + return get_string('fullnamedisplay', null, $user); + } + + // Check to see if we are displaying according to the alternative full name format. + if ($override) { + if (empty($CFG->alternativefullnameformat) || $CFG->alternativefullnameformat == 'language') { + // Default to show just the user names according to the fullnamedisplay string. + return get_string('fullnamedisplay', null, $user); + } else { + // If the override is true, then change the template to use the complete name. + $template = $CFG->alternativefullnameformat; + } + } + + $requirednames = array(); + // With each name, see if it is in the display name template, and add it to the required names array if it is. + foreach ($allnames as $allname) { + if (strpos($template, $allname) !== false) { + $requirednames[] = $allname; + } + } + + $displayname = $template; + // Switch in the actual data into the template. + foreach ($requirednames as $altname) { + if (isset($user->$altname)) { + // Using empty() on the below if statement causes breakages. + if ((string)$user->$altname == '') { + $displayname = str_replace($altname, 'EMPTY', $displayname); + } else { + $displayname = str_replace($altname, $user->$altname, $displayname); + } + } else { + $displayname = str_replace($altname, 'EMPTY', $displayname); + } + } + // Tidy up any misc. characters (Not perfect, but gets most characters). + // Don't remove the "u" at the end of the first expression unless you want garbled characters when combining hiragana or + // katakana and parenthesis. + $patterns = array(); + // This regular expression replacement is to fix problems such as 'James () Kirk' Where 'Tiberius' (middlename) has not been + // filled in by a user. + // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:). + $patterns[] = '/[[:punct:]「」]*EMPTY[[:punct:]「」]*/u'; + // This regular expression is to remove any double spaces in the display name. + $patterns[] = '/\s{2,}/u'; + foreach ($patterns as $pattern) { + $displayname = preg_replace($pattern, ' ', $displayname); + } + + // Trimming $displayname will help the next check to ensure that we don't have a display name with spaces. + $displayname = trim($displayname); + if (empty($displayname)) { + // Going with just the first name if no alternate fields are filled out. May be changed later depending on what + // people in general feel is a good setting to fall back on. + $displayname = $user->firstname; + } + return $displayname; + } + + /** + * Return profile url depending on context. + * + * @param stdClass $user the person to get details of. + * @param context|null $context The context will be used to determine the visibility of the user's profile url. + * @return moodle_url Profile url of the user + */ + public static function get_profile_url(stdClass $user, context $context = null): moodle_url { + if (empty($user->id)) { + throw new coding_exception('User id is required when displaying profile url.'); + } + + // Params to be passed to the user view page. + $params = ['id' => $user->id]; + + // Get courseid if provided. + if (isset($options['courseid'])) { + $params['courseid'] = $options['courseid']; + } + + // Get courseid from context if provided. + if ($context) { + $coursecontext = $context->get_course_context(false); + if ($coursecontext) { + $params['courseid'] = $coursecontext->instanceid; + } + } + + // If courseid is not set or is set to site id, then return profile page, otherwise return view page. + if (!isset($params['courseid']) || $params['courseid'] == SITEID) { + return new moodle_url('/user/profile.php', $params); + } else { + return new moodle_url('/user/view.php', $params); + } + } + + /** + * Return user picture depending on context. + * This function should be used for displaying purposes only as the details may not be the same as it is on database. + * + * @param stdClass $user the person to get details of. + * @param context|null $context The context will be used to determine the visibility of the user's picture. + * @param array $options public properties of {@see user_picture} to be overridden + * - courseid = $this->page->course->id (course id of user profile in link) + * - size = 35 (size of image) + * - link = true (make image clickable - the link leads to user profile) + * - popup = false (open in popup) + * - alttext = true (add image alt attribute) + * - class = image class attribute (default 'userpicture') + * - visibletoscreenreaders = true (whether to be visible to screen readers) + * - includefullname = false (whether to include the user's full name together with the user picture) + * - includetoken = false (whether to use a token for authentication. True for current user, int value for other user id) + * @return user_picture User picture object + */ + public static function get_profile_picture(stdClass $user, context $context = null, array $options = []): user_picture { + // Create a new user picture object. + $userpicture = new user_picture($user); + + // Override the user picture object with the options provided. + foreach ($options as $key => $value) { + if (property_exists($userpicture, $key)) { + $userpicture->$key = $value; + } + } + + // Return the user picture. + return $userpicture; + } + } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 7f98a71e89d..71b2d4e2abe 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3595,103 +3595,16 @@ function ismoving($courseid) { * @return string */ function fullname($user, $override=false) { - global $CFG, $SESSION; + // Note: We do not intend to deprecate this function any time soon as it is too widely used at this time. + // Uses of it should be updated to use the new API and pass updated arguments. - if (!isset($user->firstname) and !isset($user->lastname)) { + // Return an empty string if there is no user. + if (empty($user)) { return ''; } - // Get all of the name fields. - $allnames = \core_user\fields::get_name_fields(); - if ($CFG->debugdeveloper) { - foreach ($allnames as $allname) { - if (!property_exists($user, $allname)) { - // If all the user name fields are not set in the user object, then notify the programmer that it needs to be fixed. - debugging('You need to update your sql to include additional name fields in the user object.', DEBUG_DEVELOPER); - // Message has been sent, no point in sending the message multiple times. - break; - } - } - } - - if (!$override) { - if (!empty($CFG->forcefirstname)) { - $user->firstname = $CFG->forcefirstname; - } - if (!empty($CFG->forcelastname)) { - $user->lastname = $CFG->forcelastname; - } - } - - if (!empty($SESSION->fullnamedisplay)) { - $CFG->fullnamedisplay = $SESSION->fullnamedisplay; - } - - $template = null; - // If the fullnamedisplay setting is available, set the template to that. - if (isset($CFG->fullnamedisplay)) { - $template = $CFG->fullnamedisplay; - } - // If the template is empty, or set to language, return the language string. - if ((empty($template) || $template == 'language') && !$override) { - return get_string('fullnamedisplay', null, $user); - } - - // Check to see if we are displaying according to the alternative full name format. - if ($override) { - if (empty($CFG->alternativefullnameformat) || $CFG->alternativefullnameformat == 'language') { - // Default to show just the user names according to the fullnamedisplay string. - return get_string('fullnamedisplay', null, $user); - } else { - // If the override is true, then change the template to use the complete name. - $template = $CFG->alternativefullnameformat; - } - } - - $requirednames = array(); - // With each name, see if it is in the display name template, and add it to the required names array if it is. - foreach ($allnames as $allname) { - if (strpos($template, $allname) !== false) { - $requirednames[] = $allname; - } - } - - $displayname = $template; - // Switch in the actual data into the template. - foreach ($requirednames as $altname) { - if (isset($user->$altname)) { - // Using empty() on the below if statement causes breakages. - if ((string)$user->$altname == '') { - $displayname = str_replace($altname, 'EMPTY', $displayname); - } else { - $displayname = str_replace($altname, $user->$altname, $displayname); - } - } else { - $displayname = str_replace($altname, 'EMPTY', $displayname); - } - } - // Tidy up any misc. characters (Not perfect, but gets most characters). - // Don't remove the "u" at the end of the first expression unless you want garbled characters when combining hiragana or - // katakana and parenthesis. - $patterns = array(); - // This regular expression replacement is to fix problems such as 'James () Kirk' Where 'Tiberius' (middlename) has not been - // filled in by a user. - // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:). - $patterns[] = '/[[:punct:]「」]*EMPTY[[:punct:]「」]*/u'; - // This regular expression is to remove any double spaces in the display name. - $patterns[] = '/\s{2,}/u'; - foreach ($patterns as $pattern) { - $displayname = preg_replace($pattern, ' ', $displayname); - } - - // Trimming $displayname will help the next check to ensure that we don't have a display name with spaces. - $displayname = trim($displayname); - if (empty($displayname)) { - // Going with just the first name if no alternate fields are filled out. May be changed later depending on what - // people in general feel is a good setting to fall back on. - $displayname = $user->firstname; - } - return $displayname; + $options = ['override' => $override]; + return core_user::get_fullname($user, null, $options); } /** diff --git a/lib/tests/user_test.php b/lib/tests/user_test.php index d99bb0564a3..2eac24a3233 100644 --- a/lib/tests/user_test.php +++ b/lib/tests/user_test.php @@ -813,4 +813,95 @@ class user_test extends \advanced_testcase { $this->assertFalse(\core_user::awaiting_action($admin)); $this->assertTrue(\core_user::awaiting_action($manager)); } + + /** + * Test for function to get user details. + * + * @covers \core_user::get_fullname + */ + public function test_display_name() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']); + $context = \context_system::instance(); + + // Show real name as the force names config are not set. + $this->assertEquals('John Doe', \core_user::get_fullname($user, $context)); + + // With override, still show real name. + $options = ['override' => true]; + $this->assertEquals('John Doe', \core_user::get_fullname($user, $context, $options)); + + // Set the force names config. + set_config('forcefirstname', 'Bruce'); + set_config('forcelastname', 'Simpson'); + + // Show forced names. + $this->assertEquals('Bruce Simpson', \core_user::get_fullname($user, $context)); + + // With override, show real name. + $options = ['override' => true]; + $this->assertEquals('John Doe', \core_user::get_fullname($user, $context, $options)); + } + + /** + * Test for function to get user details. + * + * @covers \core_user::get_profile_url + */ + public function test_display_profile_url() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']); + + // Display profile url at site context. + $this->assertEquals("https://www.example.com/moodle/user/profile.php?id={$user->id}", + \core_user::get_profile_url($user)->out()); + + // Display profile url at course context. + $course = $this->getDataGenerator()->create_course(); + $coursecontext = \context_course::instance($course->id); + $this->assertEquals("https://www.example.com/moodle/user/view.php?id={$user->id}&courseid={$course->id}", + \core_user::get_profile_url($user, $coursecontext)); + + // Throw error if userid is invalid. + unset($user->id); + $this->expectException(\coding_exception::class); + $this->expectExceptionMessage('User id is required when displaying profile url.'); + \core_user::get_profile_url($user, $coursecontext); + } + + /** + * Test for function to get user details. + * + * @covers \core_user::get_profile_picture + */ + public function test_display_profile_picture() { + global $OUTPUT, $CFG; + $this->resetAfterTest(); + + $user1 = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']); + $user2 = $this->getDataGenerator()->create_user(['picture' => 1]); + + // Display profile picture. + $context = \context_system::instance(); + // No image, show initials. + $this->assertStringContainsString("JD", + $OUTPUT->render(\core_user::get_profile_picture($user1, $context))); + // With Image. + $expectedimagesrc = $CFG->wwwroot . '/pluginfile.php/' . \context_user::instance($user2->id)->id . + '/user/icon/boost/f2?rev=1'; + $this->assertStringContainsString($expectedimagesrc, + $OUTPUT->render(\core_user::get_profile_picture($user2, $context))); + + // Display profile picture with options. + $options = ['size' => 50, 'includefullname' => true]; + $this->assertStringContainsString("JDJohn Doe", + $OUTPUT->render(\core_user::get_profile_picture($user1, $context, $options))); + + // Display profile picture with options, no link. + $options = ['link' => false]; + $this->assertEquals("JD", + $OUTPUT->render(\core_user::get_profile_picture($user1, $context, $options))); + } } diff --git a/user/upgrade.txt b/user/upgrade.txt index 2edf699e1c8..056679fa213 100644 --- a/user/upgrade.txt +++ b/user/upgrade.txt @@ -2,6 +2,11 @@ This files describes API changes for code that uses the user API. === 4.3 === +* Added new methods: + - `core_user::get_profile_picture` for retrieving user picture. + - `core_user::get_profile_url` for retrieving profile url. + - `core_user::get_fullname` for retrieving user full name. + * The following previously deprecated methods have been removed and can no longer be used: - `profile_display_fields` - `profile_edit_category`