Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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  /**
  18   * Unit tests for user/lib.php.
  19   *
  20   * @package    core_user
  21   * @category   phpunit
  22   * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot.'/user/lib.php');
  30  
  31  /**
  32   * Unit tests for user lib api.
  33   *
  34   * @package    core_user
  35   * @category   phpunit
  36   * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class core_userliblib_testcase extends advanced_testcase {
  40      /**
  41       * Test user_get_user_details_courses
  42       */
  43      public function test_user_get_user_details_courses() {
  44          global $DB;
  45  
  46          $this->resetAfterTest();
  47  
  48          // Create user and modify user profile.
  49          $user1 = $this->getDataGenerator()->create_user();
  50          $user2 = $this->getDataGenerator()->create_user();
  51          $user3 = $this->getDataGenerator()->create_user();
  52  
  53          $course1 = $this->getDataGenerator()->create_course();
  54          $coursecontext = context_course::instance($course1->id);
  55          $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
  56          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
  57          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
  58          role_assign($teacherrole->id, $user1->id, $coursecontext->id);
  59          role_assign($teacherrole->id, $user2->id, $coursecontext->id);
  60  
  61          accesslib_clear_all_caches_for_unit_testing();
  62  
  63          // Get user2 details as a user with super system capabilities.
  64          $result = user_get_user_details_courses($user2);
  65          $this->assertEquals($user2->id, $result['id']);
  66          $this->assertEquals(fullname($user2), $result['fullname']);
  67          $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
  68  
  69          $this->setUser($user1);
  70          // Get user2 details as a user who can only see this user in a course.
  71          $result = user_get_user_details_courses($user2);
  72          $this->assertEquals($user2->id, $result['id']);
  73          $this->assertEquals(fullname($user2), $result['fullname']);
  74          $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
  75  
  76          // Get user2 details as a user who doesn't share any course with user2.
  77          $this->setUser($user3);
  78          $result = user_get_user_details_courses($user2);
  79          $this->assertNull($result);
  80      }
  81  
  82      /**
  83       * Verify return when course groupmode set to 'no groups'.
  84       */
  85      public function test_user_get_user_details_courses_groupmode_nogroups() {
  86          $this->resetAfterTest();
  87  
  88          // Enrol 2 users into a course with groupmode set to 'no groups'.
  89          // Profiles should be visible.
  90          $user1 = $this->getDataGenerator()->create_user();
  91          $user2 = $this->getDataGenerator()->create_user();
  92          $course = $this->getDataGenerator()->create_course((object) ['groupmode' => 0]);
  93          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
  94          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
  95  
  96          $this->setUser($user1);
  97          $userdetails = user_get_user_details_courses($user2);
  98          $this->assertIsArray($userdetails);
  99          $this->assertEquals($user2->id, $userdetails['id']);
 100      }
 101  
 102      /**
 103       * Verify return when course groupmode set to 'separate groups'.
 104       */
 105      public function test_user_get_user_details_courses_groupmode_separate() {
 106          $this->resetAfterTest();
 107  
 108          // Enrol 2 users into a course with groupmode set to 'separate groups'.
 109          // The users are not in any groups, so profiles should be hidden (same as if they were in separate groups).
 110          $user1 = $this->getDataGenerator()->create_user();
 111          $user2 = $this->getDataGenerator()->create_user();
 112          $course = $this->getDataGenerator()->create_course((object) ['groupmode' => 1]);
 113          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 114          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 115  
 116          $this->setUser($user1);
 117          $this->assertNull(user_get_user_details_courses($user2));
 118      }
 119  
 120      /**
 121       * Verify return when course groupmode set to 'visible groups'.
 122       */
 123      public function test_user_get_user_details_courses_groupmode_visible() {
 124          $this->resetAfterTest();
 125  
 126          // Enrol 2 users into a course with groupmode set to 'visible groups'.
 127          // The users are not in any groups, and profiles should be visible because of the groupmode.
 128          $user1 = $this->getDataGenerator()->create_user();
 129          $user2 = $this->getDataGenerator()->create_user();
 130          $course = $this->getDataGenerator()->create_course((object) ['groupmode' => 2]);
 131          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
 132          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
 133  
 134          $this->setUser($user1);
 135          $userdetails = user_get_user_details_courses($user2);
 136          $this->assertIsArray($userdetails);
 137          $this->assertEquals($user2->id, $userdetails['id']);
 138      }
 139  
 140      /**
 141       * Test user_update_user.
 142       */
 143      public function test_user_update_user() {
 144          global $DB;
 145  
 146          $this->resetAfterTest();
 147  
 148          // Create user and modify user profile.
 149          $user = $this->getDataGenerator()->create_user();
 150          $user->firstname = 'Test';
 151          $user->password = 'M00dLe@T';
 152  
 153          // Update user and capture event.
 154          $sink = $this->redirectEvents();
 155          user_update_user($user);
 156          $events = $sink->get_events();
 157          $sink->close();
 158          $event = array_pop($events);
 159  
 160          // Test updated value.
 161          $dbuser = $DB->get_record('user', array('id' => $user->id));
 162          $this->assertSame($user->firstname, $dbuser->firstname);
 163          $this->assertNotSame('M00dLe@T', $dbuser->password);
 164  
 165          // Test event.
 166          $this->assertInstanceOf('\core\event\user_updated', $event);
 167          $this->assertSame($user->id, $event->objectid);
 168          $this->assertSame('user_updated', $event->get_legacy_eventname());
 169          $this->assertEventLegacyData($dbuser, $event);
 170          $this->assertEquals(context_user::instance($user->id), $event->get_context());
 171          $expectedlogdata = array(SITEID, 'user', 'update', 'view.php?id='.$user->id, '');
 172          $this->assertEventLegacyLogData($expectedlogdata, $event);
 173  
 174          // Update user with no password update.
 175          $password = $user->password = hash_internal_user_password('M00dLe@T');
 176          user_update_user($user, false);
 177          $dbuser = $DB->get_record('user', array('id' => $user->id));
 178          $this->assertSame($password, $dbuser->password);
 179  
 180          // Verify event is not triggred by user_update_user when needed.
 181          $sink = $this->redirectEvents();
 182          user_update_user($user, false, false);
 183          $events = $sink->get_events();
 184          $sink->close();
 185          $this->assertCount(0, $events);
 186  
 187          // With password, there should be 1 event.
 188          $sink = $this->redirectEvents();
 189          user_update_user($user, true, false);
 190          $events = $sink->get_events();
 191          $sink->close();
 192          $this->assertCount(1, $events);
 193          $event = array_pop($events);
 194          $this->assertInstanceOf('\core\event\user_password_updated', $event);
 195  
 196          // Test user data validation.
 197          $user->username = 'johndoe123';
 198          $user->auth = 'shibolth';
 199          $user->country = 'WW';
 200          $user->lang = 'xy';
 201          $user->theme = 'somewrongthemename';
 202          $user->timezone = '30.5';
 203          $user->url = 'wwww.somewrong@#$url.com.aus';
 204          $debugmessages = $this->getDebuggingMessages();
 205          user_update_user($user, true, false);
 206          $this->assertDebuggingCalledCount(6, $debugmessages);
 207  
 208          // Now, with valid user data.
 209          $user->username = 'johndoe321';
 210          $user->auth = 'shibboleth';
 211          $user->country = 'AU';
 212          $user->lang = 'en';
 213          $user->theme = 'classic';
 214          $user->timezone = 'Australia/Perth';
 215          $user->url = 'www.moodle.org';
 216          user_update_user($user, true, false);
 217          $this->assertDebuggingNotCalled();
 218      }
 219  
 220      /**
 221       * Test create_users.
 222       */
 223      public function test_create_users() {
 224          global $DB;
 225  
 226          $this->resetAfterTest();
 227  
 228          $user = array(
 229              'username' => 'usernametest1',
 230              'password' => 'Moodle2012!',
 231              'idnumber' => 'idnumbertest1',
 232              'firstname' => 'First Name User Test 1',
 233              'lastname' => 'Last Name User Test 1',
 234              'middlename' => 'Middle Name User Test 1',
 235              'lastnamephonetic' => '最後のお名前のテスト一号',
 236              'firstnamephonetic' => 'お名前のテスト一号',
 237              'alternatename' => 'Alternate Name User Test 1',
 238              'email' => 'usertest1@example.com',
 239              'description' => 'This is a description for user 1',
 240              'city' => 'Perth',
 241              'country' => 'AU'
 242              );
 243  
 244          // Create user and capture event.
 245          $sink = $this->redirectEvents();
 246          $user['id'] = user_create_user($user);
 247          $events = $sink->get_events();
 248          $sink->close();
 249          $event = array_pop($events);
 250  
 251          // Test user info in DB.
 252          $dbuser = $DB->get_record('user', array('id' => $user['id']));
 253          $this->assertEquals($dbuser->username, $user['username']);
 254          $this->assertEquals($dbuser->idnumber, $user['idnumber']);
 255          $this->assertEquals($dbuser->firstname, $user['firstname']);
 256          $this->assertEquals($dbuser->lastname, $user['lastname']);
 257          $this->assertEquals($dbuser->email, $user['email']);
 258          $this->assertEquals($dbuser->description, $user['description']);
 259          $this->assertEquals($dbuser->city, $user['city']);
 260          $this->assertEquals($dbuser->country, $user['country']);
 261  
 262          // Test event.
 263          $this->assertInstanceOf('\core\event\user_created', $event);
 264          $this->assertEquals($user['id'], $event->objectid);
 265          $this->assertEquals('user_created', $event->get_legacy_eventname());
 266          $this->assertEquals(context_user::instance($user['id']), $event->get_context());
 267          $this->assertEventLegacyData($dbuser, $event);
 268          $expectedlogdata = array(SITEID, 'user', 'add', '/view.php?id='.$event->objectid, fullname($dbuser));
 269          $this->assertEventLegacyLogData($expectedlogdata, $event);
 270  
 271          // Verify event is not triggred by user_create_user when needed.
 272          $user = array('username' => 'usernametest2'); // Create another user.
 273          $sink = $this->redirectEvents();
 274          user_create_user($user, true, false);
 275          $events = $sink->get_events();
 276          $sink->close();
 277          $this->assertCount(0, $events);
 278  
 279          // Test user data validation, first some invalid data.
 280          $user['username'] = 'johndoe123';
 281          $user['auth'] = 'shibolth';
 282          $user['country'] = 'WW';
 283          $user['lang'] = 'xy';
 284          $user['theme'] = 'somewrongthemename';
 285          $user['timezone'] = '-30.5';
 286          $user['url'] = 'wwww.somewrong@#$url.com.aus';
 287          $debugmessages = $this->getDebuggingMessages();
 288          $user['id'] = user_create_user($user, true, false);
 289          $this->assertDebuggingCalledCount(6, $debugmessages);
 290          $dbuser = $DB->get_record('user', array('id' => $user['id']));
 291          $this->assertEquals($dbuser->country, 0);
 292          $this->assertEquals($dbuser->lang, 'en');
 293          $this->assertEquals($dbuser->timezone, '');
 294  
 295          // Now, with valid user data.
 296          $user['username'] = 'johndoe321';
 297          $user['auth'] = 'shibboleth';
 298          $user['country'] = 'AU';
 299          $user['lang'] = 'en';
 300          $user['theme'] = 'classic';
 301          $user['timezone'] = 'Australia/Perth';
 302          $user['url'] = 'www.moodle.org';
 303          user_create_user($user, true, false);
 304          $this->assertDebuggingNotCalled();
 305      }
 306  
 307      /**
 308       * Test that {@link user_create_user()} throws exception when invalid username is provided.
 309       *
 310       * @dataProvider data_create_user_invalid_username
 311       * @param string $username Invalid username
 312       * @param string $expectmessage Expected exception message
 313       */
 314      public function test_create_user_invalid_username($username, $expectmessage) {
 315          global $CFG;
 316  
 317          $this->resetAfterTest();
 318          $CFG->extendedusernamechars = false;
 319  
 320          $user = [
 321              'username' => $username,
 322          ];
 323  
 324          $this->expectException('moodle_exception');
 325          $this->expectExceptionMessage($expectmessage);
 326  
 327          user_create_user($user);
 328      }
 329  
 330      /**
 331       * Data provider for {@link self::test_create_user_invalid_username()}.
 332       *
 333       * @return array
 334       */
 335      public function data_create_user_invalid_username() {
 336          return [
 337              'empty_string' => [
 338                  '',
 339                  'The username cannot be blank',
 340              ],
 341              'only_whitespace' => [
 342                  "\t\t  \t\n ",
 343                  'The username cannot be blank',
 344              ],
 345              'lower_case' => [
 346                  'Mudrd8mz',
 347                  'The username must be in lower case',
 348              ],
 349              'extended_chars' => [
 350                  'dmudrák',
 351                  'The given username contains invalid characters',
 352              ],
 353          ];
 354      }
 355  
 356      /**
 357       * Test function user_count_login_failures().
 358       */
 359      public function test_user_count_login_failures() {
 360          $this->resetAfterTest();
 361          $user = $this->getDataGenerator()->create_user();
 362          $this->assertEquals(0, get_user_preferences('login_failed_count_since_success', 0, $user));
 363          for ($i = 0; $i < 10; $i++) {
 364              login_attempt_failed($user);
 365          }
 366          $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
 367          $count = user_count_login_failures($user); // Reset count.
 368          $this->assertEquals(10, $count);
 369          $this->assertEquals(0, get_user_preferences('login_failed_count_since_success', 0, $user));
 370  
 371          for ($i = 0; $i < 10; $i++) {
 372              login_attempt_failed($user);
 373          }
 374          $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
 375          $count = user_count_login_failures($user, false); // Do not reset count.
 376          $this->assertEquals(10, $count);
 377          $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
 378      }
 379  
 380      /**
 381       * Test function user_add_password_history().
 382       */
 383      public function test_user_add_password_history() {
 384          global $DB;
 385  
 386          $this->resetAfterTest();
 387  
 388          $user1 = $this->getDataGenerator()->create_user();
 389          $user2 = $this->getDataGenerator()->create_user();
 390          $user3 = $this->getDataGenerator()->create_user();
 391          $DB->delete_records('user_password_history', array());
 392  
 393          set_config('passwordreuselimit', 0);
 394  
 395          user_add_password_history($user1->id, 'pokus');
 396          $this->assertEquals(0, $DB->count_records('user_password_history'));
 397  
 398          // Test adding and discarding of old.
 399  
 400          set_config('passwordreuselimit', 3);
 401  
 402          user_add_password_history($user1->id, 'pokus');
 403          $this->assertEquals(1, $DB->count_records('user_password_history'));
 404          $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
 405  
 406          user_add_password_history($user1->id, 'pokus2');
 407          user_add_password_history($user1->id, 'pokus3');
 408          user_add_password_history($user1->id, 'pokus4');
 409          $this->assertEquals(3, $DB->count_records('user_password_history'));
 410          $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user1->id)));
 411  
 412          user_add_password_history($user2->id, 'pokus1');
 413          $this->assertEquals(4, $DB->count_records('user_password_history'));
 414          $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user1->id)));
 415          $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user2->id)));
 416  
 417          user_add_password_history($user2->id, 'pokus2');
 418          user_add_password_history($user2->id, 'pokus3');
 419          $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user2->id)));
 420  
 421          $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 422          user_add_password_history($user2->id, 'pokus4');
 423          $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user2->id)));
 424          $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 425  
 426          $removed = array_shift($ids);
 427          $added = array_pop($newids);
 428          $this->assertSame($ids, $newids);
 429          $this->assertGreaterThan($removed, $added);
 430  
 431          // Test disabling prevents changes.
 432  
 433          set_config('passwordreuselimit', 0);
 434  
 435          $this->assertEquals(6, $DB->count_records('user_password_history'));
 436  
 437          $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 438          user_add_password_history($user2->id, 'pokus5');
 439          user_add_password_history($user3->id, 'pokus1');
 440          $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 441          $this->assertSame($ids, $newids);
 442          $this->assertEquals(6, $DB->count_records('user_password_history'));
 443  
 444          set_config('passwordreuselimit', -1);
 445  
 446          $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 447          user_add_password_history($user2->id, 'pokus6');
 448          user_add_password_history($user3->id, 'pokus6');
 449          $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
 450          $this->assertSame($ids, $newids);
 451          $this->assertEquals(6, $DB->count_records('user_password_history'));
 452      }
 453  
 454      /**
 455       * Test function user_add_password_history().
 456       */
 457      public function test_user_is_previously_used_password() {
 458          global $DB;
 459  
 460          $this->resetAfterTest();
 461  
 462          $user1 = $this->getDataGenerator()->create_user();
 463          $user2 = $this->getDataGenerator()->create_user();
 464          $DB->delete_records('user_password_history', array());
 465  
 466          set_config('passwordreuselimit', 0);
 467  
 468          user_add_password_history($user1->id, 'pokus');
 469          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus'));
 470  
 471          set_config('passwordreuselimit', 3);
 472  
 473          user_add_password_history($user2->id, 'pokus1');
 474          user_add_password_history($user2->id, 'pokus2');
 475  
 476          user_add_password_history($user1->id, 'pokus1');
 477          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
 478          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
 479          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
 480          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
 481  
 482          user_add_password_history($user1->id, 'pokus2');
 483          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
 484          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
 485          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
 486          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
 487  
 488          user_add_password_history($user1->id, 'pokus3');
 489          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
 490          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
 491          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
 492          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
 493  
 494          user_add_password_history($user1->id, 'pokus4');
 495          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
 496          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
 497          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
 498          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
 499  
 500          set_config('passwordreuselimit', 2);
 501  
 502          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
 503          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
 504          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
 505          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
 506  
 507          set_config('passwordreuselimit', 3);
 508  
 509          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
 510          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
 511          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
 512          $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
 513  
 514          set_config('passwordreuselimit', 0);
 515  
 516          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
 517          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
 518          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
 519          $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
 520      }
 521  
 522      /**
 523       * Test that password history is deleted together with user.
 524       */
 525      public function test_delete_of_hashes_on_user_delete() {
 526          global $DB;
 527  
 528          $this->resetAfterTest();
 529  
 530          $user1 = $this->getDataGenerator()->create_user();
 531          $user2 = $this->getDataGenerator()->create_user();
 532          $DB->delete_records('user_password_history', array());
 533  
 534          set_config('passwordreuselimit', 3);
 535  
 536          user_add_password_history($user1->id, 'pokus');
 537          user_add_password_history($user2->id, 'pokus1');
 538          user_add_password_history($user2->id, 'pokus2');
 539  
 540          $this->assertEquals(3, $DB->count_records('user_password_history'));
 541          $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
 542          $this->assertEquals(2, $DB->count_records('user_password_history', array('userid' => $user2->id)));
 543  
 544          delete_user($user2);
 545          $this->assertEquals(1, $DB->count_records('user_password_history'));
 546          $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
 547          $this->assertEquals(0, $DB->count_records('user_password_history', array('userid' => $user2->id)));
 548      }
 549  
 550      /**
 551       * Test user_list_view function
 552       */
 553      public function test_user_list_view() {
 554  
 555          $this->resetAfterTest();
 556  
 557          // Course without sections.
 558          $course = $this->getDataGenerator()->create_course();
 559          $context = context_course::instance($course->id);
 560  
 561          $this->setAdminUser();
 562  
 563          // Redirect events to the sink, so we can recover them later.
 564          $sink = $this->redirectEvents();
 565  
 566          user_list_view($course, $context);
 567          $events = $sink->get_events();
 568          $this->assertCount(1, $events);
 569          $event = reset($events);
 570  
 571          // Check the event details are correct.
 572          $this->assertInstanceOf('\core\event\user_list_viewed', $event);
 573          $this->assertEquals($context, $event->get_context());
 574          $this->assertEquals($course->shortname, $event->other['courseshortname']);
 575          $this->assertEquals($course->fullname, $event->other['coursefullname']);
 576  
 577      }
 578  
 579      /**
 580       * Test setting the user menu avatar size.
 581       */
 582      public function test_user_menu_custom_avatar_size() {
 583          global $PAGE;
 584          $this->resetAfterTest(true);
 585  
 586          $testsize = 100;
 587  
 588          $PAGE->set_url('/');
 589          $user = $this->getDataGenerator()->create_user();
 590          $opts = user_get_user_navigation_info($user, $PAGE, array('avatarsize' => $testsize));
 591          $avatarhtml = $opts->metadata['useravatar'];
 592  
 593          $matches = [];
 594          preg_match('/(?:.*width=")(\d*)(?:" height=")(\d*)(?:".*\/>)/', $avatarhtml, $matches);
 595          $this->assertCount(3, $matches);
 596  
 597          $this->assertEquals(intval($matches[1]), $testsize);
 598          $this->assertEquals(intval($matches[2]), $testsize);
 599      }
 600  
 601      /**
 602       * Test user_can_view_profile
 603       */
 604      public function test_user_can_view_profile() {
 605          global $DB, $CFG;
 606  
 607          $this->resetAfterTest();
 608  
 609          // Create five users.
 610          $user1 = $this->getDataGenerator()->create_user();
 611          $user2 = $this->getDataGenerator()->create_user();
 612          $user3 = $this->getDataGenerator()->create_user();
 613          $user4 = $this->getDataGenerator()->create_user();
 614          $user5 = $this->getDataGenerator()->create_user();
 615          $user6 = $this->getDataGenerator()->create_user(array('deleted' => 1));
 616          $user7 = $this->getDataGenerator()->create_user();
 617          $user8 = $this->getDataGenerator()->create_user();
 618          $user8->id = 0; // Visitor.
 619  
 620          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 621          // Add the course creator role to the course contact and assign a user to that role.
 622          $CFG->coursecontact = '2';
 623          $coursecreatorrole = $DB->get_record('role', array('shortname' => 'coursecreator'));
 624          $this->getDataGenerator()->role_assign($coursecreatorrole->id, $user7->id);
 625  
 626           // Create two courses.
 627          $course1 = $this->getDataGenerator()->create_course();
 628          $course2 = $this->getDataGenerator()->create_course();
 629          $coursecontext = context_course::instance($course2->id);
 630          // Prepare another course with separate groups and groupmodeforce set to true.
 631          $record = new stdClass();
 632          $record->groupmode = 1;
 633          $record->groupmodeforce = 1;
 634          $course3 = $this->getDataGenerator()->create_course($record);
 635          // Enrol users 1 and 2 in first course.
 636          $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
 637          $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
 638          // Enrol users 2 and 3 in second course.
 639          $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
 640          $this->getDataGenerator()->enrol_user($user3->id, $course2->id);
 641          // Enrol users 1, 4, and 5 into course 3.
 642          $this->getDataGenerator()->enrol_user($user1->id, $course3->id);
 643          $this->getDataGenerator()->enrol_user($user4->id, $course3->id);
 644          $this->getDataGenerator()->enrol_user($user5->id, $course3->id);
 645  
 646          // User 3 should not be able to see user 1, either by passing their own course (course 2) or user 1's course (course 1).
 647          $this->setUser($user3);
 648          $this->assertFalse(user_can_view_profile($user1, $course2));
 649          $this->assertFalse(user_can_view_profile($user1, $course1));
 650  
 651          // Remove capability moodle/user:viewdetails in course 2.
 652          assign_capability('moodle/user:viewdetails', CAP_PROHIBIT, $studentrole->id, $coursecontext);
 653          // Set current user to user 1.
 654          $this->setUser($user1);
 655          // User 1 can see User 1's profile.
 656          $this->assertTrue(user_can_view_profile($user1));
 657  
 658          $tempcfg = $CFG->forceloginforprofiles;
 659          $CFG->forceloginforprofiles = 0;
 660          // Not forced to log in to view profiles, should be able to see all profiles besides user 6.
 661          $users = array($user1, $user2, $user3, $user4, $user5, $user7);
 662          foreach ($users as $user) {
 663              $this->assertTrue(user_can_view_profile($user));
 664          }
 665          // Restore setting.
 666          $CFG->forceloginforprofiles = $tempcfg;
 667  
 668          // User 1 can not see user 6 as they have been deleted.
 669          $this->assertFalse(user_can_view_profile($user6));
 670          // User 1 can see User 7 as they are a course contact.
 671          $this->assertTrue(user_can_view_profile($user7));
 672          // User 1 is in a course with user 2 and has the right capability - return true.
 673          $this->assertTrue(user_can_view_profile($user2));
 674          // User 1 is not in a course with user 3 - return false.
 675          $this->assertFalse(user_can_view_profile($user3));
 676  
 677          // Set current user to user 2.
 678          $this->setUser($user2);
 679          // User 2 is in a course with user 3 but does not have the right capability - return false.
 680          $this->assertFalse(user_can_view_profile($user3));
 681  
 682          // Set user 1 in one group and users 4 and 5 in another group.
 683          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
 684          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
 685          groups_add_member($group1->id, $user1->id);
 686          groups_add_member($group2->id, $user4->id);
 687          groups_add_member($group2->id, $user5->id);
 688          $this->setUser($user1);
 689          // Check that user 1 can not see user 4.
 690          $this->assertFalse(user_can_view_profile($user4));
 691          // Check that user 5 can see user 4.
 692          $this->setUser($user5);
 693          $this->assertTrue(user_can_view_profile($user4));
 694  
 695          // Test the user:viewalldetails cap check using the course creator role which, by default, can't see student profiles.
 696          $this->setUser($user7);
 697          $this->assertFalse(user_can_view_profile($user4));
 698          assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $coursecreatorrole->id, context_system::instance()->id, true);
 699          reload_all_capabilities();
 700          $this->assertTrue(user_can_view_profile($user4));
 701          unassign_capability('moodle/user:viewalldetails', $coursecreatorrole->id, $coursecontext->id);
 702          reload_all_capabilities();
 703  
 704          $CFG->coursecontact = null;
 705  
 706          // Visitor (Not a guest user, userid=0).
 707          $CFG->forceloginforprofiles = 1;
 708          $this->setUser($user8);
 709          $this->assertFalse(user_can_view_profile($user1));
 710  
 711          // Let us test with guest user.
 712          $this->setGuestUser();
 713          $CFG->forceloginforprofiles = 1;
 714          foreach ($users as $user) {
 715              $this->assertFalse(user_can_view_profile($user));
 716          }
 717  
 718          // Even with cap, still guests should not be allowed in.
 719          $guestrole = $DB->get_records_menu('role', array('shortname' => 'guest'), 'id', 'archetype, id');
 720          assign_capability('moodle/user:viewdetails', CAP_ALLOW, $guestrole['guest'], context_system::instance()->id, true);
 721          reload_all_capabilities();
 722          foreach ($users as $user) {
 723              $this->assertFalse(user_can_view_profile($user));
 724          }
 725  
 726          $CFG->forceloginforprofiles = 0;
 727          foreach ($users as $user) {
 728              $this->assertTrue(user_can_view_profile($user));
 729          }
 730  
 731          // Let us test with Visitor user.
 732          $this->setUser($user8);
 733          $CFG->forceloginforprofiles = 1;
 734          foreach ($users as $user) {
 735              $this->assertFalse(user_can_view_profile($user));
 736          }
 737  
 738          $CFG->forceloginforprofiles = 0;
 739          foreach ($users as $user) {
 740              $this->assertTrue(user_can_view_profile($user));
 741          }
 742  
 743          // Testing non-shared courses where capabilities are met, using system role overrides.
 744          $CFG->forceloginforprofiles = $tempcfg;
 745          $course4 = $this->getDataGenerator()->create_course();
 746          $this->getDataGenerator()->enrol_user($user1->id, $course4->id);
 747  
 748          // Assign a manager role at the system context.
 749          $managerrole = $DB->get_record('role', array('shortname' => 'manager'));
 750          $user9 = $this->getDataGenerator()->create_user();
 751          $this->getDataGenerator()->role_assign($managerrole->id, $user9->id);
 752  
 753          // Make sure viewalldetails and viewdetails are overridden to 'prevent' (i.e. can be overridden at a lower context).
 754          $systemcontext = context_system::instance();
 755          assign_capability('moodle/user:viewdetails', CAP_PREVENT, $managerrole->id, $systemcontext, true);
 756          assign_capability('moodle/user:viewalldetails', CAP_PREVENT, $managerrole->id, $systemcontext, true);
 757  
 758          // And override these to 'Allow' in a specific course.
 759          $course4context = context_course::instance($course4->id);
 760          assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $managerrole->id, $course4context, true);
 761          assign_capability('moodle/user:viewdetails', CAP_ALLOW, $managerrole->id, $course4context, true);
 762  
 763          // The manager now shouldn't have viewdetails in the system or user context.
 764          $this->setUser($user9);
 765          $user1context = context_user::instance($user1->id);
 766          $this->assertFalse(has_capability('moodle/user:viewdetails', $systemcontext));
 767          $this->assertFalse(has_capability('moodle/user:viewdetails', $user1context));
 768  
 769          // Confirm that user_can_view_profile() returns true for $user1 when called without $course param. It should find $course1.
 770          $this->assertTrue(user_can_view_profile($user1));
 771  
 772          // Confirm this also works when restricting scope to just that course.
 773          $this->assertTrue(user_can_view_profile($user1, $course4));
 774      }
 775  
 776      /**
 777       * Test user_get_user_details
 778       */
 779      public function test_user_get_user_details() {
 780          global $DB;
 781  
 782          $this->resetAfterTest();
 783  
 784          // Create user and modify user profile.
 785          $teacher = $this->getDataGenerator()->create_user();
 786          $student = $this->getDataGenerator()->create_user();
 787          $studentfullname = fullname($student);
 788  
 789          $course1 = $this->getDataGenerator()->create_course();
 790          $coursecontext = context_course::instance($course1->id);
 791          $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
 792          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 793          $this->getDataGenerator()->enrol_user($teacher->id, $course1->id);
 794          $this->getDataGenerator()->enrol_user($student->id, $course1->id);
 795          role_assign($teacherrole->id, $teacher->id, $coursecontext->id);
 796          role_assign($studentrole->id, $student->id, $coursecontext->id);
 797  
 798          accesslib_clear_all_caches_for_unit_testing();
 799  
 800          // Get student details as a user with super system capabilities.
 801          $result = user_get_user_details($student, $course1);
 802          $this->assertEquals($student->id, $result['id']);
 803          $this->assertEquals($studentfullname, $result['fullname']);
 804          $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
 805  
 806          $this->setUser($teacher);
 807          // Get student details as a user who can only see this user in a course.
 808          $result = user_get_user_details($student, $course1);
 809          $this->assertEquals($student->id, $result['id']);
 810          $this->assertEquals($studentfullname, $result['fullname']);
 811          $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
 812  
 813          // Get student details with required fields.
 814          $result = user_get_user_details($student, $course1, array('id', 'fullname'));
 815          $this->assertCount(2, $result);
 816          $this->assertEquals($student->id, $result['id']);
 817          $this->assertEquals($studentfullname, $result['fullname']);
 818  
 819          // Get exception for invalid required fields.
 820          $this->expectException('moodle_exception');
 821          $result = user_get_user_details($student, $course1, array('wrongrequiredfield'));
 822      }
 823  
 824      /**
 825       * Regression test for MDL-57840.
 826       *
 827       * Ensure the fields "auth, confirmed, idnumber, lang, theme, timezone and mailformat" are present when
 828       * calling user_get_user_details() function.
 829       */
 830      public function test_user_get_user_details_missing_fields() {
 831          global $CFG;
 832  
 833          $this->resetAfterTest(true);
 834          $this->setAdminUser(); // We need capabilities to view the data.
 835          $user = self::getDataGenerator()->create_user([
 836                                                            'auth'       => 'email',
 837                                                            'confirmed'  => '0',
 838                                                            'idnumber'   => 'someidnumber',
 839                                                            'lang'       => 'en',
 840                                                            'theme'      => $CFG->theme,
 841                                                            'timezone'   => '5',
 842                                                            'mailformat' => '0',
 843                                                        ]);
 844  
 845          // Fields that should get by default.
 846          $got = user_get_user_details($user);
 847          self::assertSame('email', $got['auth']);
 848          self::assertSame('0', $got['confirmed']);
 849          self::assertSame('someidnumber', $got['idnumber']);
 850          self::assertSame('en', $got['lang']);
 851          self::assertSame($CFG->theme, $got['theme']);
 852          self::assertSame('5', $got['timezone']);
 853          self::assertSame('0', $got['mailformat']);
 854      }
 855  }