Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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