Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core;
  18  
  19  /**
  20   * Test core_user class.
  21   *
  22   * @package    core
  23   * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  class user_test extends \advanced_testcase {
  27  
  28      /**
  29       * Setup test data.
  30       */
  31      protected function setUp(): void {
  32          $this->resetAfterTest(true);
  33      }
  34  
  35      public function test_get_user() {
  36          global $CFG;
  37  
  38  
  39          // Create user and try fetach it with api.
  40          $user = $this->getDataGenerator()->create_user();
  41          $this->assertEquals($user, \core_user::get_user($user->id, '*', MUST_EXIST));
  42  
  43          // Test noreply user.
  44          $CFG->noreplyuserid = null;
  45          $noreplyuser = \core_user::get_noreply_user();
  46          $this->assertEquals(1, $noreplyuser->emailstop);
  47          $this->assertFalse(\core_user::is_real_user($noreplyuser->id));
  48          $this->assertEquals($CFG->noreplyaddress, $noreplyuser->email);
  49          $this->assertEquals(get_string('noreplyname'), $noreplyuser->firstname);
  50  
  51          // Set user as noreply user and make sure noreply propery is set.
  52          \core_user::reset_internal_users();
  53          $CFG->noreplyuserid = $user->id;
  54          $noreplyuser = \core_user::get_noreply_user();
  55          $this->assertEquals(1, $noreplyuser->emailstop);
  56          $this->assertTrue(\core_user::is_real_user($noreplyuser->id));
  57  
  58          // Test support user.
  59          \core_user::reset_internal_users();
  60          $CFG->supportemail = null;
  61          $CFG->noreplyuserid = null;
  62          $supportuser = \core_user::get_support_user();
  63          $adminuser = get_admin();
  64          $this->assertEquals($adminuser, $supportuser);
  65          $this->assertTrue(\core_user::is_real_user($supportuser->id));
  66  
  67          // When supportemail is set.
  68          \core_user::reset_internal_users();
  69          $CFG->supportemail = 'test@example.com';
  70          $supportuser = \core_user::get_support_user();
  71          $this->assertEquals(\core_user::SUPPORT_USER, $supportuser->id);
  72          $this->assertFalse(\core_user::is_real_user($supportuser->id));
  73  
  74          // Set user as support user and make sure noreply propery is set.
  75          \core_user::reset_internal_users();
  76          $CFG->supportuserid = $user->id;
  77          $supportuser = \core_user::get_support_user();
  78          $this->assertEquals($user, $supportuser);
  79          $this->assertTrue(\core_user::is_real_user($supportuser->id));
  80      }
  81  
  82      /**
  83       * Test get_user_by_username method.
  84       */
  85      public function test_get_user_by_username() {
  86          $record = array();
  87          $record['username'] = 'johndoe';
  88          $record['email'] = 'johndoe@example.com';
  89          $record['timecreated'] = time();
  90  
  91          // Create a default user for the test.
  92          $userexpected = $this->getDataGenerator()->create_user($record);
  93  
  94          // Assert that the returned user is the espected one.
  95          $this->assertEquals($userexpected, \core_user::get_user_by_username('johndoe'));
  96  
  97          // Assert that a subset of fields is correctly returned.
  98          $this->assertEquals((object) $record, \core_user::get_user_by_username('johndoe', 'username,email,timecreated'));
  99  
 100          // Assert that a user with a different mnethostid will no be returned.
 101          $this->assertFalse(\core_user::get_user_by_username('johndoe', 'username,email,timecreated', 2));
 102  
 103          // Create a new user from a different host.
 104          $record['mnethostid'] = 2;
 105          $userexpected2 = $this->getDataGenerator()->create_user($record);
 106  
 107          // Assert that the new user is returned when specified the correct mnethostid.
 108          $this->assertEquals($userexpected2, \core_user::get_user_by_username('johndoe', '*', 2));
 109  
 110          // Assert that a user not in the db return false.
 111          $this->assertFalse(\core_user::get_user_by_username('janedoe'));
 112      }
 113  
 114      public function test_search() {
 115          global $DB;
 116  
 117          self::init_search_tests();
 118  
 119          // Set up three courses for test.
 120          $generator = $this->getDataGenerator();
 121          $course1 = $generator->create_course();
 122          $course2 = $generator->create_course();
 123          $course3 = $generator->create_course();
 124  
 125          // Manager user in system level.
 126          $manager = $generator->create_user(['firstname' => 'Manager', 'lastname' => 'Person',
 127                  'email' => 'x@x.x']);
 128          $systemcontext = \context_system::instance();
 129          $generator->role_assign($DB->get_field('role', 'id', ['shortname' => 'manager']),
 130                  $manager->id, $systemcontext->id);
 131  
 132          // Teachers in one and two courses.
 133          $teacher1 = $generator->create_user(['firstname' => 'Alberto', 'lastname' => 'Unwin',
 134                  'email' => 'a.unwin@x.x']);
 135          $generator->enrol_user($teacher1->id, $course1->id, 'teacher');
 136          $teacher2and3 = $generator->create_user(['firstname' => 'Alexandra', 'lastname' => 'Penguin',
 137                  'email' => 'sillypenguin@x.x']);
 138          $generator->enrol_user($teacher2and3->id, $course2->id, 'teacher');
 139          $generator->enrol_user($teacher2and3->id, $course3->id, 'teacher');
 140  
 141          // Students in each course and some on multiple courses.
 142          $student1 = $generator->create_user(['firstname' => 'Amanda', 'lastname' => 'Hodder',
 143                  'email' => 'hodder_a@x.x']);
 144          $generator->enrol_user($student1->id, $course1->id, 'student');
 145          $student2 = $generator->create_user(['firstname' => 'Audrey', 'lastname' => 'Methuen',
 146                  'email' => 'audrey@x.x']);
 147          $generator->enrol_user($student2->id, $course2->id, 'student');
 148          $student3 = $generator->create_user(['firstname' => 'Austin', 'lastname' => 'Bloomsbury',
 149                  'email' => 'a.bloomsbury@x.x']);
 150          $generator->enrol_user($student3->id, $course3->id, 'student');
 151          $student1and2 = $generator->create_user(['firstname' => 'Augustus', 'lastname' => 'Random',
 152                  'email' => 'random@x.x']);
 153          $generator->enrol_user($student1and2->id, $course1->id, 'student');
 154          $generator->enrol_user($student1and2->id, $course2->id, 'student');
 155          $studentall = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'House',
 156                  'email' => 'house@x.x']);
 157          $generator->enrol_user($studentall->id, $course1->id, 'student');
 158          $generator->enrol_user($studentall->id, $course2->id, 'student');
 159          $generator->enrol_user($studentall->id, $course3->id, 'student');
 160  
 161          // Special mixed user (name does not begin with A) is a teacher in one course and student
 162          // in another.
 163          $mixed = $generator->create_user(['firstname' => 'Xavier', 'lastname' => 'Harper',
 164                  'email' => 'xh1248@x.x']);
 165          $generator->enrol_user($mixed->id, $course1->id, 'student');
 166          $generator->enrol_user($mixed->id, $course3->id, 'teacher');
 167  
 168          // As admin user, try searching for somebody at system level by first name, checking the
 169          // results.
 170          $this->setAdminUser();
 171          $result = \core_user::search('Amelia');
 172          $this->assertCount(1, $result);
 173  
 174          // Check some basic fields, and test other fields are present.
 175          $this->assertEquals($studentall->id, $result[0]->id);
 176          $this->assertEquals('Amelia', $result[0]->firstname);
 177          $this->assertEquals('House', $result[0]->lastname);
 178          $this->assertEquals('house@x.x', $result[0]->email);
 179          $this->assertEquals(0, $result[0]->deleted);
 180          $this->assertObjectHasAttribute('firstnamephonetic', $result[0]);
 181          $this->assertObjectHasAttribute('lastnamephonetic', $result[0]);
 182          $this->assertObjectHasAttribute('middlename', $result[0]);
 183          $this->assertObjectHasAttribute('alternatename', $result[0]);
 184          $this->assertObjectHasAttribute('imagealt', $result[0]);
 185          $this->assertObjectHasAttribute('username', $result[0]);
 186  
 187          // Now search by lastname, both names, and partials, case-insensitive.
 188          $this->assertEquals($result, \core_user::search('House'));
 189          $this->assertEquals($result, \core_user::search('Amelia house'));
 190          $this->assertEquals($result, \core_user::search('amelI'));
 191          $this->assertEquals($result, \core_user::search('hoUs'));
 192          $this->assertEquals($result, \core_user::search('Amelia H'));
 193  
 194          // Admin user can also search by email (full or partial).
 195          $this->assertEquals($result, \core_user::search('house@x.x'));
 196          $this->assertEquals($result, \core_user::search('hOuse@'));
 197  
 198          // What if we just search for A? (They all begin with A except the manager.)
 199          $result = \core_user::search('a');
 200          $this->assertCount(7, $result);
 201  
 202          // Au gets us Audrey, Austin, and Augustus - in alphabetical order by surname.
 203          $result = \core_user::search('au');
 204          $this->assertCount(3, $result);
 205          $this->assertEquals('Austin', $result[0]->firstname);
 206          $this->assertEquals('Audrey', $result[1]->firstname);
 207          $this->assertEquals('Augustus', $result[2]->firstname);
 208  
 209          // But if we search within course 2 we'll get Audrey and Augustus first.
 210          $course2context = \context_course::instance($course2->id);
 211          $result = \core_user::search('au', $course2context);
 212          $this->assertCount(3, $result);
 213          $this->assertEquals('Audrey', $result[0]->firstname);
 214          $this->assertEquals('Augustus', $result[1]->firstname);
 215          $this->assertEquals('Austin', $result[2]->firstname);
 216  
 217          // Try doing a few searches as manager - we should get the same results and can still
 218          // search by email too.
 219          $this->setUser($manager);
 220          $result = \core_user::search('a');
 221          $this->assertCount(7, $result);
 222          $result = \core_user::search('au', $course2context);
 223          $this->assertCount(3, $result);
 224          $result = \core_user::search('house@x.x');
 225          $this->assertCount(1, $result);
 226  
 227          // Teacher 1. No site-level permission so can't see users outside the enrolled course.
 228          $this->setUser($teacher1);
 229          $result = \core_user::search('au');
 230          $this->assertCount(1, $result);
 231          $this->assertEquals('Augustus', $result[0]->firstname);
 232  
 233          // Can still search by email for that user.
 234          $result = \core_user::search('random@x.x');
 235          $this->assertCount(1, $result);
 236  
 237          // Search everyone - teacher can only see four users (including themself).
 238          $result = \core_user::search('a');
 239          $this->assertCount(4, $result);
 240  
 241          // Search within course 2 - you get the same four users (which doesn't include
 242          // everyone on that course) but the two on course 2 should be first.
 243          $result = \core_user::search('a', $course2context);
 244          $this->assertCount(4, $result);
 245          $this->assertEquals('Amelia', $result[0]->firstname);
 246          $this->assertEquals('Augustus', $result[1]->firstname);
 247  
 248          // Other teacher.
 249          $this->setUser($teacher2and3);
 250          $result = \core_user::search('au');
 251          $this->assertCount(3, $result);
 252  
 253          $result = \core_user::search('a');
 254          $this->assertCount(5, $result);
 255  
 256          // Student can only see users on course 3.
 257          $this->setUser($student3);
 258          $result = \core_user::search('a');
 259          $this->assertCount(3, $result);
 260  
 261          $result = \core_user::search('au');
 262          $this->assertCount(1, $result);
 263          $this->assertEquals('Austin', $result[0]->firstname);
 264  
 265          // Student cannot search by email.
 266          $result = \core_user::search('a.bloomsbury@x.x');
 267          $this->assertCount(0, $result);
 268  
 269          // Student on all courses can see all the A users.
 270          $this->setUser($studentall);
 271          $result = \core_user::search('a');
 272          $this->assertCount(7, $result);
 273  
 274          // Mixed user can see users on courses 1 and 3.
 275          $this->setUser($mixed);
 276          $result = \core_user::search('a');
 277          $this->assertCount(6, $result);
 278  
 279          // Mixed user can search by email for students on course 3 but not on course 1.
 280          $result = \core_user::search('hodder_a@x.x');
 281          $this->assertCount(0, $result);
 282          $result = \core_user::search('house@x.x');
 283          $this->assertCount(1, $result);
 284      }
 285  
 286      /**
 287       * Tests the search() function with limits on the number to return.
 288       */
 289      public function test_search_with_count() {
 290          self::init_search_tests();
 291          $generator = $this->getDataGenerator();
 292          $course = $generator->create_course();
 293  
 294          // Check default limit (30).
 295          for ($i = 0; $i < 31; $i++) {
 296              $student = $generator->create_user(['firstname' => 'Guy', 'lastname' => 'Xxx' . $i,
 297                      'email' => 'xxx@x.x']);
 298              $generator->enrol_user($student->id, $course->id, 'student');
 299          }
 300          $this->setAdminUser();
 301          $result = \core_user::search('Guy');
 302          $this->assertCount(30, $result);
 303  
 304          // Check a small limit.
 305          $result = \core_user::search('Guy', null, 10);
 306          $this->assertCount(10, $result);
 307  
 308          // Check no limit.
 309          $result = \core_user::search('Guy', null, 0);
 310          $this->assertCount(31, $result);
 311      }
 312  
 313      /**
 314       * When course is in separate groups mode and user is a student, they can't see people who
 315       * are not in the same group. This is checked by the user profile permission thing and not
 316       * currently by the original query.
 317       */
 318      public function test_search_group_permissions() {
 319          global $DB;
 320  
 321          self::init_search_tests();
 322  
 323          // Create one user to do the searching.
 324          $generator = $this->getDataGenerator();
 325          $course = $generator->create_course(['groupmode' => SEPARATEGROUPS]);
 326          $searcher = $generator->create_user(['firstname' => 'Searchy', 'lastname' => 'Sam',
 327                  'email' => 'xxx@x.x']);
 328          $generator->enrol_user($searcher->id, $course->id, 'student');
 329          $group = $generator->create_group(['courseid' => $course->id]);
 330          groups_add_member($group, $searcher);
 331  
 332          // Create a large number of people so that we have to make multiple database reads.
 333          $targets = [];
 334          for ($i = 0; $i < 50; $i++) {
 335              $student = $generator->create_user(['firstname' => 'Guy', 'lastname' => 'Xxx' . $i,
 336                      'email' => 'xxx@x.x']);
 337              $generator->enrol_user($student->id, $course->id, 'student');
 338              $targets[] = $student;
 339          }
 340  
 341          // The first and last people are in the same group.
 342          groups_add_member($group, $targets[0]);
 343          groups_add_member($group, $targets[49]);
 344  
 345          // As searcher, we only find the 2 in the same group.
 346          $this->setUser($searcher);
 347          $result = \core_user::search('Guy');
 348          $this->assertCount(2, $result);
 349  
 350          // If we change the course to visible groups though, we get the max number.
 351          $DB->set_field('course', 'groupmode', VISIBLEGROUPS, ['id' => $course->id]);
 352          $result = \core_user::search('Guy');
 353          $this->assertCount(30, $result);
 354      }
 355  
 356      /**
 357       * When course is in separate groups mode and user is a student, they can't see people who
 358       * are not in the same group. This is checked by the user profile permission thing and not
 359       * currently by the original query.
 360       */
 361      public function test_search_deleted_users() {
 362          self::init_search_tests();
 363  
 364          // Create one user to do the searching.
 365          $generator = $this->getDataGenerator();
 366          $course = $generator->create_course();
 367          $searcher = $generator->create_user(['firstname' => 'Searchy', 'lastname' => 'Sam',
 368                  'email' => 'xxx@x.x']);
 369          $generator->enrol_user($searcher->id, $course->id, 'student');
 370  
 371          // Create another two users to search for.
 372          $student1 = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'Aardvark']);
 373          $student2 = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'Beetle']);
 374          $generator->enrol_user($student1->id, $course->id, 'student');
 375          $generator->enrol_user($student2->id, $course->id, 'student');
 376  
 377          // As searcher, we find both users.
 378          $this->setUser($searcher);
 379          $result = \core_user::search('Amelia');
 380          $this->assertCount(2, $result);
 381  
 382          // What if one is deleted?
 383          delete_user($student1);
 384          $result = \core_user::search('Amelia');
 385          $this->assertCount(1, $result);
 386          $this->assertEquals('Beetle', $result[0]->lastname);
 387  
 388          // Delete the other, for good measure.
 389          delete_user($student2);
 390          $result = \core_user::search('Amelia');
 391          $this->assertCount(0, $result);
 392      }
 393  
 394      /**
 395       * Carries out standard setup for the search test functions.
 396       */
 397      protected static function init_search_tests() {
 398          global $DB;
 399  
 400          // For all existing users, set their name and email to something stupid so we don't
 401          // accidentally find one, confusing the test counts.
 402          $DB->set_field('user', 'firstname', 'Zaphod');
 403          $DB->set_field('user', 'lastname', 'Beeblebrox');
 404          $DB->set_field('user', 'email', 'zaphod@beeblebrox.example.org');
 405  
 406          // This is the default value, but let's set it just to be certain in case it changes later.
 407          // It affects what fields admin (and other users with the viewuseridentity permission) can
 408          // search in addition to the name.
 409          set_config('showuseridentity', 'email');
 410      }
 411  
 412      /**
 413       * Test require_active_user
 414       */
 415      public function test_require_active_user() {
 416          global $DB;
 417  
 418          // Create a default user for the test.
 419          $userexpected = $this->getDataGenerator()->create_user();
 420  
 421          // Simple case, all good.
 422          \core_user::require_active_user($userexpected, true, true);
 423  
 424          // Set user not confirmed.
 425          $DB->set_field('user', 'confirmed', 0, array('id' => $userexpected->id));
 426          try {
 427              \core_user::require_active_user($userexpected);
 428          } catch (\moodle_exception $e) {
 429              $this->assertEquals('usernotconfirmed', $e->errorcode);
 430          }
 431          $DB->set_field('user', 'confirmed', 1, array('id' => $userexpected->id));
 432  
 433          // Set nologin auth method.
 434          $DB->set_field('user', 'auth', 'nologin', array('id' => $userexpected->id));
 435          try {
 436              \core_user::require_active_user($userexpected, false, true);
 437          } catch (\moodle_exception $e) {
 438              $this->assertEquals('suspended', $e->errorcode);
 439          }
 440          // Check no exceptions are thrown if we don't specify to check suspended.
 441          \core_user::require_active_user($userexpected);
 442          $DB->set_field('user', 'auth', 'manual', array('id' => $userexpected->id));
 443  
 444          // Set user suspended.
 445          $DB->set_field('user', 'suspended', 1, array('id' => $userexpected->id));
 446          try {
 447              \core_user::require_active_user($userexpected, true);
 448          } catch (\moodle_exception $e) {
 449              $this->assertEquals('suspended', $e->errorcode);
 450          }
 451          // Check no exceptions are thrown if we don't specify to check suspended.
 452          \core_user::require_active_user($userexpected);
 453  
 454          // Delete user.
 455          delete_user($userexpected);
 456          try {
 457              \core_user::require_active_user($userexpected);
 458          } catch (\moodle_exception $e) {
 459              $this->assertEquals('userdeleted', $e->errorcode);
 460          }
 461  
 462          // Use a not real user.
 463          $noreplyuser = \core_user::get_noreply_user();
 464          try {
 465              \core_user::require_active_user($noreplyuser, true);
 466          } catch (\moodle_exception $e) {
 467              $this->assertEquals('invaliduser', $e->errorcode);
 468          }
 469  
 470          // Get the guest user.
 471          $guestuser = $DB->get_record('user', array('username' => 'guest'));
 472          try {
 473              \core_user::require_active_user($guestuser, true);
 474          } catch (\moodle_exception $e) {
 475              $this->assertEquals('guestsarenotallowed', $e->errorcode);
 476          }
 477  
 478      }
 479  
 480      /**
 481       * Test get_property_definition() method.
 482       */
 483      public function test_get_property_definition() {
 484          // Try to get a existing property.
 485          $properties = \core_user::get_property_definition('id');
 486          $this->assertEquals($properties['type'], PARAM_INT);
 487          $properties = \core_user::get_property_definition('username');
 488          $this->assertEquals($properties['type'], PARAM_USERNAME);
 489  
 490          // Invalid property.
 491          try {
 492              \core_user::get_property_definition('fullname');
 493          } catch (\coding_exception $e) {
 494              $this->assertMatchesRegularExpression('/Invalid property requested./', $e->getMessage());
 495          }
 496  
 497          // Empty parameter.
 498          try {
 499              \core_user::get_property_definition('');
 500          } catch (\coding_exception $e) {
 501              $this->assertMatchesRegularExpression('/Invalid property requested./', $e->getMessage());
 502          }
 503      }
 504  
 505      /**
 506       * Test validate() method.
 507       */
 508      public function test_validate() {
 509  
 510          // Create user with just with username and firstname.
 511          $record = array('username' => 's10', 'firstname' => 'Bebe Stevens');
 512          $validation = \core_user::validate((object)$record);
 513  
 514          // Validate the user, should return true as the user data is correct.
 515          $this->assertTrue($validation);
 516  
 517          // Create user with incorrect data (invalid country and theme).
 518          $record = array('username' => 's1', 'firstname' => 'Eric Cartman', 'country' => 'UU', 'theme' => 'beise');
 519  
 520          // Should return an array with 2 errors.
 521          $validation = \core_user::validate((object)$record);
 522          $this->assertArrayHasKey('country', $validation);
 523          $this->assertArrayHasKey('theme', $validation);
 524          $this->assertCount(2, $validation);
 525  
 526          // Create user with malicious data (xss).
 527          $record = array('username' => 's3', 'firstname' => 'Kyle<script>alert(1);<script> Broflovski');
 528  
 529          // Should return an array with 1 error.
 530          $validation = \core_user::validate((object)$record);
 531          $this->assertCount(1, $validation);
 532          $this->assertArrayHasKey('firstname', $validation);
 533      }
 534  
 535      /**
 536       * Test clean_data() method.
 537       */
 538      public function test_clean_data() {
 539          $this->resetAfterTest(false);
 540  
 541          $user = new \stdClass();
 542          $user->firstname = 'John <script>alert(1)</script> Doe';
 543          $user->username = 'john%#&~%*_doe';
 544          $user->email = ' john@testing.com ';
 545          $user->deleted = 'no';
 546          $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
 547          $usercleaned = \core_user::clean_data($user);
 548  
 549          // Expected results.
 550          $this->assertEquals('John alert(1) Doe', $usercleaned->firstname);
 551          $this->assertEquals('john@testing.com', $usercleaned->email);
 552          $this->assertEquals(0, $usercleaned->deleted);
 553          $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', $user->description);
 554          $this->assertEquals('john_doe', $user->username);
 555  
 556          // Try to clean an invalid property (userfullname).
 557          $user->userfullname = 'John Doe';
 558          \core_user::clean_data($user);
 559          $this->assertDebuggingCalled("The property 'userfullname' could not be cleaned.");
 560      }
 561  
 562      /**
 563       * Test clean_field() method.
 564       */
 565      public function test_clean_field() {
 566  
 567          // Create a 'malicious' user object/
 568          $user = new \stdClass();
 569          $user->firstname = 'John <script>alert(1)</script> Doe';
 570          $user->username = 'john%#&~%*_doe';
 571          $user->email = ' john@testing.com ';
 572          $user->deleted = 'no';
 573          $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
 574          $user->userfullname = 'John Doe';
 575  
 576          // Expected results.
 577          $this->assertEquals('John alert(1) Doe', \core_user::clean_field($user->firstname, 'firstname'));
 578          $this->assertEquals('john_doe', \core_user::clean_field($user->username, 'username'));
 579          $this->assertEquals('john@testing.com', \core_user::clean_field($user->email, 'email'));
 580          $this->assertEquals(0, \core_user::clean_field($user->deleted, 'deleted'));
 581          $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', \core_user::clean_field($user->description, 'description'));
 582  
 583          // Try to clean an invalid property (fullname).
 584          \core_user::clean_field($user->userfullname, 'fullname');
 585          $this->assertDebuggingCalled("The property 'fullname' could not be cleaned.");
 586      }
 587  
 588      /**
 589       * Test get_property_type() method.
 590       */
 591      public function test_get_property_type() {
 592  
 593          // Fetch valid properties and verify if the type is correct.
 594          $type = \core_user::get_property_type('username');
 595          $this->assertEquals(PARAM_USERNAME, $type);
 596          $type = \core_user::get_property_type('email');
 597          $this->assertEquals(PARAM_RAW_TRIMMED, $type);
 598          $type = \core_user::get_property_type('timezone');
 599          $this->assertEquals(PARAM_TIMEZONE, $type);
 600  
 601          // Try to fetch type of a non-existent properties.
 602          $nonexistingproperty = 'userfullname';
 603          $this->expectException('coding_exception');
 604          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 605          \core_user::get_property_type($nonexistingproperty);
 606          $nonexistingproperty = 'mobilenumber';
 607          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 608          \core_user::get_property_type($nonexistingproperty);
 609      }
 610  
 611      /**
 612       * Test get_property_null() method.
 613       */
 614      public function test_get_property_null() {
 615          // Fetch valid properties and verify if it is NULL_ALLOWED or NULL_NOT_ALLOWED.
 616          $property = \core_user::get_property_null('username');
 617          $this->assertEquals(NULL_NOT_ALLOWED, $property);
 618          $property = \core_user::get_property_null('password');
 619          $this->assertEquals(NULL_NOT_ALLOWED, $property);
 620          $property = \core_user::get_property_null('imagealt');
 621          $this->assertEquals(NULL_ALLOWED, $property);
 622          $property = \core_user::get_property_null('middlename');
 623          $this->assertEquals(NULL_ALLOWED, $property);
 624  
 625          // Try to fetch type of a non-existent properties.
 626          $nonexistingproperty = 'lastnamefonetic';
 627          $this->expectException('coding_exception');
 628          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 629          \core_user::get_property_null($nonexistingproperty);
 630          $nonexistingproperty = 'midlename';
 631          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 632          \core_user::get_property_null($nonexistingproperty);
 633      }
 634  
 635      /**
 636       * Test get_property_choices() method.
 637       */
 638      public function test_get_property_choices() {
 639  
 640          // Test against country property choices.
 641          $choices = \core_user::get_property_choices('country');
 642          $this->assertArrayHasKey('AU', $choices);
 643          $this->assertArrayHasKey('BR', $choices);
 644          $this->assertArrayNotHasKey('WW', $choices);
 645          $this->assertArrayNotHasKey('TX', $choices);
 646  
 647          // Test against lang property choices.
 648          $choices = \core_user::get_property_choices('lang');
 649          $this->assertArrayHasKey('en', $choices);
 650          $this->assertArrayNotHasKey('ww', $choices);
 651          $this->assertArrayNotHasKey('yy', $choices);
 652  
 653          // Test against theme property choices.
 654          $choices = \core_user::get_property_choices('theme');
 655          $this->assertArrayHasKey('boost', $choices);
 656          $this->assertArrayHasKey('classic', $choices);
 657          $this->assertArrayNotHasKey('unknowntheme', $choices);
 658          $this->assertArrayNotHasKey('wrongtheme', $choices);
 659  
 660          // Try to fetch type of a non-existent properties.
 661          $nonexistingproperty = 'language';
 662          $this->expectException('coding_exception');
 663          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 664          \core_user::get_property_null($nonexistingproperty);
 665          $nonexistingproperty = 'coutries';
 666          $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
 667          \core_user::get_property_null($nonexistingproperty);
 668      }
 669  
 670      /**
 671       * Test get_property_default().
 672       */
 673      public function test_get_property_default() {
 674          global $CFG;
 675          $this->resetAfterTest();
 676  
 677          $country = \core_user::get_property_default('country');
 678          $this->assertEquals($CFG->country, $country);
 679          set_config('country', 'AU');
 680          \core_user::reset_caches();
 681          $country = \core_user::get_property_default('country');
 682          $this->assertEquals($CFG->country, $country);
 683  
 684          $lang = \core_user::get_property_default('lang');
 685          $this->assertEquals($CFG->lang, $lang);
 686          set_config('lang', 'en');
 687          $lang = \core_user::get_property_default('lang');
 688          $this->assertEquals($CFG->lang, $lang);
 689  
 690          $this->setTimezone('Europe/London', 'Pacific/Auckland');
 691          \core_user::reset_caches();
 692          $timezone = \core_user::get_property_default('timezone');
 693          $this->assertEquals('Europe/London', $timezone);
 694          $this->setTimezone('99', 'Pacific/Auckland');
 695          \core_user::reset_caches();
 696          $timezone = \core_user::get_property_default('timezone');
 697          $this->assertEquals('Pacific/Auckland', $timezone);
 698  
 699          $this->expectException(\coding_exception::class);
 700          $this->expectExceptionMessage('Invalid property requested, or the property does not has a default value.');
 701          \core_user::get_property_default('firstname');
 702      }
 703  
 704      /**
 705       * Ensure that the noreply user is not cached.
 706       */
 707      public function test_get_noreply_user() {
 708          global $CFG;
 709  
 710          // Create a new fake language 'xx' with the 'noreplyname'.
 711          $langfolder = $CFG->dataroot . '/lang/xx';
 712          check_dir_exists($langfolder);
 713          $langconfig = "<?php\n\defined('MOODLE_INTERNAL') || die();";
 714          file_put_contents($langfolder . '/langconfig.php', $langconfig);
 715          $langconfig = "<?php\n\$string['noreplyname'] = 'XXX';";
 716          file_put_contents($langfolder . '/moodle.php', $langconfig);
 717  
 718          $CFG->lang='en';
 719          $enuser = \core_user::get_noreply_user();
 720  
 721          $CFG->lang='xx';
 722          $xxuser = \core_user::get_noreply_user();
 723  
 724          $this->assertNotEquals($enuser, $xxuser);
 725      }
 726  
 727      /**
 728       * Test is_real_user method.
 729       */
 730      public function test_is_real_user() {
 731          global $CFG, $USER;
 732  
 733          // Real users are real users.
 734          $auser = $this->getDataGenerator()->create_user();
 735          $guest = guest_user();
 736          $this->assertTrue(\core_user::is_real_user($auser->id));
 737          $this->assertTrue(\core_user::is_real_user($auser->id, true));
 738          $this->assertTrue(\core_user::is_real_user($guest->id));
 739          $this->assertTrue(\core_user::is_real_user($guest->id, true));
 740  
 741          // Non-logged in users are not real users.
 742          $this->assertSame(0, $USER->id, 'The non-logged in user should have an ID of 0.');
 743          $this->assertFalse(\core_user::is_real_user($USER->id));
 744          $this->assertFalse(\core_user::is_real_user($USER->id, true));
 745  
 746          // Other types of logged in users are real users.
 747          $this->setAdminUser();
 748          $this->assertTrue(\core_user::is_real_user($USER->id));
 749          $this->assertTrue(\core_user::is_real_user($USER->id, true));
 750          $this->setGuestUser();
 751          $this->assertTrue(\core_user::is_real_user($USER->id));
 752          $this->assertTrue(\core_user::is_real_user($USER->id, true));
 753          $this->setUser($auser);
 754          $this->assertTrue(\core_user::is_real_user($USER->id));
 755          $this->assertTrue(\core_user::is_real_user($USER->id, true));
 756  
 757          // Fake accounts are not real users.
 758          $CFG->noreplyuserid = null;
 759          $this->assertFalse(\core_user::is_real_user(\core_user::get_noreply_user()->id));
 760          $this->assertFalse(\core_user::is_real_user(\core_user::get_noreply_user()->id, true));
 761          $CFG->supportuserid = null;
 762          $CFG->supportemail = 'test@example.com';
 763          $this->assertFalse(\core_user::is_real_user(\core_user::get_support_user()->id));
 764          $this->assertFalse(\core_user::is_real_user(\core_user::get_support_user()->id, true));
 765      }
 766  
 767      /**
 768       * Tests for the {@see \core_user::awaiting_action()} method.
 769       */
 770      public function test_awaiting_action() {
 771          global $CFG, $DB, $USER;
 772  
 773          $guest = \core_user::get_user($CFG->siteguest);
 774          $student = $this->getDataGenerator()->create_user();
 775          $teacher = $this->getDataGenerator()->create_user();
 776          $manager = $this->getDataGenerator()->create_user();
 777          $admin = get_admin();
 778  
 779          $this->getDataGenerator()->role_assign($DB->get_field('role', 'id', ['shortname' => 'manager']),
 780              $manager->id, \context_system::instance()->id);
 781  
 782          // Scenario: Guests required to agree to site policy.
 783          $this->assertFalse(\core_user::awaiting_action($guest));
 784  
 785          $CFG->sitepolicyguest = 'https://example.com';
 786          $this->assertTrue(\core_user::awaiting_action($guest));
 787  
 788          $guest->policyagreed = 1;
 789          $this->assertFalse(\core_user::awaiting_action($guest));
 790  
 791          // Scenario: Student required to fill their profile.
 792          $this->assertFalse(\core_user::awaiting_action($student));
 793  
 794          $student->firstname = '';
 795          $this->assertTrue(\core_user::awaiting_action($student));
 796  
 797          $student->firstname = 'Alice';
 798          $this->assertFalse(\core_user::awaiting_action($student));
 799  
 800          // Scenario: Teacher force to change their password.
 801          $this->assertFalse(\core_user::awaiting_action($teacher));
 802  
 803          set_user_preference('auth_forcepasswordchange', 1, $teacher);
 804          $this->assertTrue(\core_user::awaiting_action($teacher));
 805  
 806          unset_user_preference('auth_forcepasswordchange', $teacher);
 807          $this->assertFalse(\core_user::awaiting_action($teacher));
 808  
 809          // Scenario: Admins do not need to agree to the policy but others do.
 810          $this->assertFalse(\core_user::awaiting_action($admin));
 811          $this->assertFalse(\core_user::awaiting_action($manager));
 812          $CFG->sitepolicy = 'https://example.com';
 813          $this->assertFalse(\core_user::awaiting_action($admin));
 814          $this->assertTrue(\core_user::awaiting_action($manager));
 815      }
 816  
 817      /**
 818       * Test that user with Letter avatar respect language preference.
 819       *
 820       * @param array $userdata
 821       * @param string $fullnameconfig
 822       * @param string $expected
 823       * @return void
 824       * @covers       \core_user::get_initials
 825       * @dataProvider user_name_provider
 826       */
 827      public function test_get_initials(array $userdata, string $fullnameconfig, string $expected): void {
 828          $this->resetAfterTest();
 829          // Create a user.
 830          $page = new \moodle_page();
 831          $page->set_url('/user/profile.php');
 832          $page->set_context(\context_system::instance());
 833          $renderer = $page->get_renderer('core');
 834          $user1 =
 835              $this->getDataGenerator()->create_user(
 836                  array_merge(
 837                      ['picture' => 0, 'email' => 'user1@example.com'],
 838                      $userdata
 839                  )
 840              );
 841          set_config('fullnamedisplay', $fullnameconfig);
 842          $initials = \core_user::get_initials($user1);
 843          $this->assertEquals($expected, $initials);
 844      }
 845  
 846      /**
 847       * Provider of user configuration for testing initials rendering
 848       *
 849       * @return array[]
 850       */
 851      public static function user_name_provider(): array {
 852          return [
 853              'simple user' => [
 854                  'user' => ['firstname' => 'first', 'lastname' => 'last'],
 855                  'fullnamedisplay' => 'language',
 856                  'expected' => 'fl',
 857              ],
 858              'simple user with lastname firstname in language settings' => [
 859                  'user' => ['firstname' => 'first', 'lastname' => 'last'],
 860                  'fullnamedisplay' => 'lastname firstname',
 861                  'expected' => 'lf',
 862              ],
 863              'simple user with no surname' => [
 864                  'user' => ['firstname' => '', 'lastname' => 'L'],
 865                  'fullnamedisplay' => 'language',
 866                  'expected' => 'L',
 867              ],
 868              'simple user with a middle name' => [
 869                  'user' => ['firstname' => 'f', 'lastname' => 'l', 'middlename' => 'm'],
 870                  'fullnamedisplay' => 'middlename lastname',
 871                  'expected' => 'ml',
 872              ],
 873              'user with a middle name & fullnamedisplay contains 3 names' => [
 874                  'user' => ['firstname' => 'first', 'lastname' => 'last', 'middlename' => 'middle'],
 875                  'fullnamedisplay' => 'firstname middlename lastname',
 876                  'expected' => 'fl',
 877              ],
 878              'simple user with a namefield consisting of one element' => [
 879                  'user' => ['firstname' => 'first', 'lastname' => 'last'],
 880                  'fullnamedisplay' => 'lastname',
 881                  'expected' => 'l',
 882              ],
 883          ];
 884      }
 885  }