Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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