From d20f1024d84e407a5320a9f69c464238054d0bbb Mon Sep 17 00:00:00 2001
From: Laurent David <laurent.david@moodle.com>
Date: Sun, 21 May 2023 23:20:07 +0900
Subject: [PATCH] MDL-77274 core_renderer: Respect full name format in letter
 avatars

* Make sure that we respect the fullnamedisplay and alternativefullnameformat
parameters to decide on the initials for a given user
* Add further tests

Co-authored-by: Tatsunori Uchino <tats.u@live.jp>
---
 lib/classes/user.php    | 27 ++++++++++++++++
 lib/outputrenderers.php |  8 +++--
 lib/tests/user_test.php | 69 +++++++++++++++++++++++++++++++++++++++++
 lib/upgrade.txt         |  3 ++
 4 files changed, 105 insertions(+), 2 deletions(-)

diff --git a/lib/classes/user.php b/lib/classes/user.php
index 0593334241d..889cc050301 100644
--- a/lib/classes/user.php
+++ b/lib/classes/user.php
@@ -1226,4 +1226,31 @@ class core_user {
         };
         return null;
     }
+
+
+    /**
+     * Get initials for users
+     *
+     * @param stdClass $user
+     * @return string
+     */
+    public static function get_initials(stdClass $user): string {
+        // Get the available name fields.
+        $namefields = \core_user\fields::get_name_fields();
+        // Build a dummy user to determine the name format.
+        $dummyuser = array_combine($namefields, $namefields);
+        // Determine the name format by using fullname() and passing the dummy user.
+        $nameformat = fullname((object) $dummyuser);
+        // Fetch all the available username fields.
+        $availablefields = order_in_string($namefields, $nameformat);
+        // We only want the first and last name fields.
+        if (!empty($availablefields) && count($availablefields) >= 2) {
+            $availablefields = [reset($availablefields), end($availablefields)];
+        }
+        $initials = '';
+        foreach ($availablefields as $userfieldname) {
+            $initials .= mb_substr($user->$userfieldname, 0, 1);
+        }
+        return $initials;
+    }
 }
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index 6e1ce2a4bc3..079bf5681a0 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -2668,8 +2668,12 @@ class core_renderer extends renderer_base {
 
         // Get the image html output first, auto generated based on initials if one isn't already set.
         if ($user->picture == 0 && empty($CFG->enablegravatar) && !defined('BEHAT_SITE_RUNNING')) {
-            $output = html_writer::tag('span', mb_substr($user->firstname, 0, 1) . mb_substr($user->lastname, 0, 1),
-                ['class' => 'userinitials size-' . $size]);
+            $initials = \core_user::get_initials($user);
+            // Don't modify in corner cases where neither the firstname nor the lastname appears.
+            $output = html_writer::tag(
+                'span', $initials,
+                ['class' => 'userinitials size-' . $size]
+            );
         } else {
             $output = html_writer::empty_tag('img', $attributes);
         }
diff --git a/lib/tests/user_test.php b/lib/tests/user_test.php
index d99bb0564a3..607ef61979b 100644
--- a/lib/tests/user_test.php
+++ b/lib/tests/user_test.php
@@ -813,4 +813,73 @@ class user_test extends \advanced_testcase {
         $this->assertFalse(\core_user::awaiting_action($admin));
         $this->assertTrue(\core_user::awaiting_action($manager));
     }
+
+    /**
+     * Test that user with Letter avatar respect language preference.
+     *
+     * @param array $userdata
+     * @param string $fullnameconfig
+     * @param string $expected
+     * @return void
+     * @covers       \core_user::get_initials
+     * @dataProvider user_name_provider
+     */
+    public function test_get_initials(array $userdata, string $fullnameconfig, string $expected): void {
+        $this->resetAfterTest();
+        // Create a user.
+        $page = new \moodle_page();
+        $page->set_url('/user/profile.php');
+        $page->set_context(\context_system::instance());
+        $renderer = $page->get_renderer('core');
+        $user1 =
+            $this->getDataGenerator()->create_user(
+                array_merge(
+                    ['picture' => 0, 'email' => 'user1@example.com'],
+                    $userdata
+                )
+            );
+        set_config('fullnamedisplay', $fullnameconfig);
+        $initials = \core_user::get_initials($user1);
+        $this->assertEquals($expected, $initials);
+    }
+
+    /**
+     * Provider of user configuration for testing initials rendering
+     *
+     * @return array[]
+     */
+    public static function user_name_provider(): array {
+        return [
+            'simple user' => [
+                'user' => ['firstname' => 'first', 'lastname' => 'last'],
+                'fullnamedisplay' => 'language',
+                'expected' => 'fl',
+            ],
+            'simple user with lastname firstname in language settings' => [
+                'user' => ['firstname' => 'first', 'lastname' => 'last'],
+                'fullnamedisplay' => 'lastname firstname',
+                'expected' => 'lf',
+            ],
+            'simple user with no surname' => [
+                'user' => ['firstname' => '', 'lastname' => 'L'],
+                'fullnamedisplay' => 'language',
+                'expected' => 'L',
+            ],
+            'simple user with a middle name' => [
+                'user' => ['firstname' => 'f', 'lastname' => 'l', 'middlename' => 'm'],
+                'fullnamedisplay' => 'middlename lastname',
+                'expected' => 'ml',
+            ],
+            'user with a middle name & fullnamedisplay contains 3 names' => [
+                'user' => ['firstname' => 'first', 'lastname' => 'last', 'middlename' => 'middle'],
+                'fullnamedisplay' => 'firstname middlename lastname',
+                'expected' => 'fl',
+            ],
+            'simple user with a namefield consisting of one element' => [
+                'user' => ['firstname' => 'first', 'lastname' => 'last'],
+                'fullnamedisplay' => 'lastname',
+                'expected' => 'l',
+            ],
+        ];
+    }
 }
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index 9da0ce9a40d..74eb47cbc30 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -1,6 +1,9 @@
 This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
+=== 4.1.7 ===
+* Add a new method core_user::get_initials to get the initials of a user in a way compatible with internationalisation.
+
 === 4.1.6 ===
 * \moodle_page::set_title() has been updated to append the site name depending on the value of $CFG->sitenameintitle and whether
   the site's fullname/shortname has been set. So there's no need to manually add the site name whenever calling $PAGE->set_title().