Merge branch 'MDL-80633-main' of https://github.com/laurentdavid/moodle
[moodle.git] / user / tests / userlib_test.php
blob7271b2fed40bba4500dbf5d0b5f757b1238878ad
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.
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/>.
17 namespace core_user;
19 defined('MOODLE_INTERNAL') || die();
21 global $CFG;
22 require_once($CFG->dirroot.'/user/lib.php');
24 /**
25 * Unit tests for user lib api.
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
32 class userlib_test extends \advanced_testcase {
33 /**
34 * Test user_get_user_details_courses
36 public function test_user_get_user_details_courses() {
37 global $DB;
39 $this->resetAfterTest();
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();
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);
54 accesslib_clear_all_caches_for_unit_testing();
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']);
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']);
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);
75 /**
76 * Verify return when course groupmode set to 'no groups'.
78 public function test_user_get_user_details_courses_groupmode_nogroups() {
79 $this->resetAfterTest();
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);
89 $this->setUser($user1);
90 $userdetails = user_get_user_details_courses($user2);
91 $this->assertIsArray($userdetails);
92 $this->assertEquals($user2->id, $userdetails['id']);
95 /**
96 * Verify return when course groupmode set to 'separate groups'.
98 public function test_user_get_user_details_courses_groupmode_separate() {
99 $this->resetAfterTest();
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);
109 $this->setUser($user1);
110 $this->assertNull(user_get_user_details_courses($user2));
114 * Verify return when course groupmode set to 'visible groups'.
116 public function test_user_get_user_details_courses_groupmode_visible() {
117 $this->resetAfterTest();
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);
127 $this->setUser($user1);
128 $userdetails = user_get_user_details_courses($user2);
129 $this->assertIsArray($userdetails);
130 $this->assertEquals($user2->id, $userdetails['id']);
134 * Tests that the user fields returned by the method can be limited.
136 * @covers ::user_get_user_details_courses
138 public function test_user_get_user_details_courses_limit_return() {
139 $this->resetAfterTest();
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);
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());
152 $minimaluser = (object) [
153 'id' => $user2->id,
154 'deleted' => $user2->deleted,
157 foreach ($namefields as $field) {
158 $minimaluser->$field = $user2->$field;
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']);
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);
177 * Test user_update_user.
179 public function test_user_update_user() {
180 global $DB;
182 $this->resetAfterTest();
184 // Create user and modify user profile.
185 $user = $this->getDataGenerator()->create_user();
186 $user->firstname = 'Test';
187 $user->password = 'M00dLe@T';
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);
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);
201 // Test event.
202 $this->assertInstanceOf('\core\event\user_updated', $event);
203 $this->assertSame($user->id, $event->objectid);
204 $this->assertEquals(\context_user::instance($user->id), $event->get_context());
206 // Update user with no password update.
207 $password = $user->password = hash_internal_user_password('M00dLe@T');
208 user_update_user($user, false);
209 $dbuser = $DB->get_record('user', array('id' => $user->id));
210 $this->assertSame($password, $dbuser->password);
212 // Verify event is not triggred by user_update_user when needed.
213 $sink = $this->redirectEvents();
214 user_update_user($user, false, false);
215 $events = $sink->get_events();
216 $sink->close();
217 $this->assertCount(0, $events);
219 // With password, there should be 1 event.
220 $sink = $this->redirectEvents();
221 user_update_user($user, true, false);
222 $events = $sink->get_events();
223 $sink->close();
224 $this->assertCount(1, $events);
225 $event = array_pop($events);
226 $this->assertInstanceOf('\core\event\user_password_updated', $event);
228 // Test user data validation.
229 $user->username = 'johndoe123';
230 $user->auth = 'shibolth';
231 $user->country = 'WW';
232 $user->lang = 'xy';
233 $user->theme = 'somewrongthemename';
234 $user->timezone = '30.5';
235 $debugmessages = $this->getDebuggingMessages();
236 user_update_user($user, true, false);
237 $this->assertDebuggingCalledCount(5, $debugmessages);
239 // Now, with valid user data.
240 $user->username = 'johndoe321';
241 $user->auth = 'shibboleth';
242 $user->country = 'AU';
243 $user->lang = 'en';
244 $user->theme = 'classic';
245 $user->timezone = 'Australia/Perth';
246 user_update_user($user, true, false);
247 $this->assertDebuggingNotCalled();
251 * Test create_users.
253 public function test_create_users() {
254 global $DB;
256 $this->resetAfterTest();
258 $user = array(
259 'username' => 'usernametest1',
260 'password' => 'Moodle2012!',
261 'idnumber' => 'idnumbertest1',
262 'firstname' => 'First Name User Test 1',
263 'lastname' => 'Last Name User Test 1',
264 'middlename' => 'Middle Name User Test 1',
265 'lastnamephonetic' => '最後のお名前のテスト一号',
266 'firstnamephonetic' => 'お名前のテスト一号',
267 'alternatename' => 'Alternate Name User Test 1',
268 'email' => 'usertest1@example.com',
269 'description' => 'This is a description for user 1',
270 'city' => 'Perth',
271 'country' => 'AU'
274 // Create user and capture event.
275 $sink = $this->redirectEvents();
276 $user['id'] = user_create_user($user);
277 $events = $sink->get_events();
278 $sink->close();
279 $event = array_pop($events);
281 // Test user info in DB.
282 $dbuser = $DB->get_record('user', array('id' => $user['id']));
283 $this->assertEquals($dbuser->username, $user['username']);
284 $this->assertEquals($dbuser->idnumber, $user['idnumber']);
285 $this->assertEquals($dbuser->firstname, $user['firstname']);
286 $this->assertEquals($dbuser->lastname, $user['lastname']);
287 $this->assertEquals($dbuser->email, $user['email']);
288 $this->assertEquals($dbuser->description, $user['description']);
289 $this->assertEquals($dbuser->city, $user['city']);
290 $this->assertEquals($dbuser->country, $user['country']);
292 // Test event.
293 $this->assertInstanceOf('\core\event\user_created', $event);
294 $this->assertEquals($user['id'], $event->objectid);
295 $this->assertEquals(\context_user::instance($user['id']), $event->get_context());
297 // Verify event is not triggred by user_create_user when needed.
298 $user = array('username' => 'usernametest2'); // Create another user.
299 $sink = $this->redirectEvents();
300 user_create_user($user, true, false);
301 $events = $sink->get_events();
302 $sink->close();
303 $this->assertCount(0, $events);
305 // Test user data validation, first some invalid data.
306 $user['username'] = 'johndoe123';
307 $user['auth'] = 'shibolth';
308 $user['country'] = 'WW';
309 $user['lang'] = 'xy';
310 $user['theme'] = 'somewrongthemename';
311 $user['timezone'] = '-30.5';
312 $debugmessages = $this->getDebuggingMessages();
313 $user['id'] = user_create_user($user, true, false);
314 $this->assertDebuggingCalledCount(5, $debugmessages);
315 $dbuser = $DB->get_record('user', array('id' => $user['id']));
316 $this->assertEquals($dbuser->country, 0);
317 $this->assertEquals($dbuser->lang, 'en');
318 $this->assertEquals($dbuser->timezone, '');
320 // Now, with valid user data.
321 $user['username'] = 'johndoe321';
322 $user['auth'] = 'shibboleth';
323 $user['country'] = 'AU';
324 $user['lang'] = 'en';
325 $user['theme'] = 'classic';
326 $user['timezone'] = 'Australia/Perth';
327 user_create_user($user, true, false);
328 $this->assertDebuggingNotCalled();
332 * Test that creating users populates default values
334 * @covers ::user_create_user
336 public function test_user_create_user_default_values(): void {
337 global $CFG;
339 $this->resetAfterTest();
341 // Update default values for city/country (both initially empty).
342 set_config('defaultcity', 'Nadi');
343 set_config('country', 'FJ');
345 $userid = user_create_user((object) [
346 'username' => 'newuser',
347 ], false, false);
349 $user = \core_user::get_user($userid);
350 $this->assertEquals($CFG->calendartype, $user->calendartype);
351 $this->assertEquals($CFG->defaultpreference_maildisplay, $user->maildisplay);
352 $this->assertEquals($CFG->defaultpreference_mailformat, $user->mailformat);
353 $this->assertEquals($CFG->defaultpreference_maildigest, $user->maildigest);
354 $this->assertEquals($CFG->defaultpreference_autosubscribe, $user->autosubscribe);
355 $this->assertEquals($CFG->defaultpreference_trackforums, $user->trackforums);
356 $this->assertEquals($CFG->lang, $user->lang);
357 $this->assertEquals($CFG->defaultcity, $user->city);
358 $this->assertEquals($CFG->country, $user->country);
362 * Test that {@link user_create_user()} throws exception when invalid username is provided.
364 * @dataProvider data_create_user_invalid_username
365 * @param string $username Invalid username
366 * @param string $expectmessage Expected exception message
368 public function test_create_user_invalid_username($username, $expectmessage) {
369 global $CFG;
371 $this->resetAfterTest();
372 $CFG->extendedusernamechars = false;
374 $user = [
375 'username' => $username,
378 $this->expectException('moodle_exception');
379 $this->expectExceptionMessage($expectmessage);
381 user_create_user($user);
385 * Data provider for {@link self::test_create_user_invalid_username()}.
387 * @return array
389 public function data_create_user_invalid_username() {
390 return [
391 'empty_string' => [
393 'The username cannot be blank',
395 'only_whitespace' => [
396 "\t\t \t\n ",
397 'The username cannot be blank',
399 'lower_case' => [
400 'Mudrd8mz',
401 'The username must be in lower case',
403 'extended_chars' => [
404 'dmudrák',
405 'The given username contains invalid characters',
411 * Test function user_count_login_failures().
413 public function test_user_count_login_failures() {
414 $this->resetAfterTest();
415 $user = $this->getDataGenerator()->create_user();
416 $this->assertEquals(0, get_user_preferences('login_failed_count_since_success', 0, $user));
417 for ($i = 0; $i < 10; $i++) {
418 login_attempt_failed($user);
420 $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
421 $count = user_count_login_failures($user); // Reset count.
422 $this->assertEquals(10, $count);
423 $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);
428 $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
429 $count = user_count_login_failures($user, false); // Do not reset count.
430 $this->assertEquals(10, $count);
431 $this->assertEquals(10, get_user_preferences('login_failed_count_since_success', 0, $user));
435 * Test function user_add_password_history().
437 public function test_user_add_password_history() {
438 global $DB;
440 $this->resetAfterTest();
442 $user1 = $this->getDataGenerator()->create_user();
443 $user2 = $this->getDataGenerator()->create_user();
444 $user3 = $this->getDataGenerator()->create_user();
445 $DB->delete_records('user_password_history', array());
447 set_config('passwordreuselimit', 0);
449 user_add_password_history($user1->id, 'pokus');
450 $this->assertEquals(0, $DB->count_records('user_password_history'));
452 // Test adding and discarding of old.
454 set_config('passwordreuselimit', 3);
456 user_add_password_history($user1->id, 'pokus');
457 $this->assertEquals(1, $DB->count_records('user_password_history'));
458 $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
460 user_add_password_history($user1->id, 'pokus2');
461 user_add_password_history($user1->id, 'pokus3');
462 user_add_password_history($user1->id, 'pokus4');
463 $this->assertEquals(3, $DB->count_records('user_password_history'));
464 $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user1->id)));
466 user_add_password_history($user2->id, 'pokus1');
467 $this->assertEquals(4, $DB->count_records('user_password_history'));
468 $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user1->id)));
469 $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user2->id)));
471 user_add_password_history($user2->id, 'pokus2');
472 user_add_password_history($user2->id, 'pokus3');
473 $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user2->id)));
475 $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
476 user_add_password_history($user2->id, 'pokus4');
477 $this->assertEquals(3, $DB->count_records('user_password_history', array('userid' => $user2->id)));
478 $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
480 $removed = array_shift($ids);
481 $added = array_pop($newids);
482 $this->assertSame($ids, $newids);
483 $this->assertGreaterThan($removed, $added);
485 // Test disabling prevents changes.
487 set_config('passwordreuselimit', 0);
489 $this->assertEquals(6, $DB->count_records('user_password_history'));
491 $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
492 user_add_password_history($user2->id, 'pokus5');
493 user_add_password_history($user3->id, 'pokus1');
494 $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
495 $this->assertSame($ids, $newids);
496 $this->assertEquals(6, $DB->count_records('user_password_history'));
498 set_config('passwordreuselimit', -1);
500 $ids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
501 user_add_password_history($user2->id, 'pokus6');
502 user_add_password_history($user3->id, 'pokus6');
503 $newids = array_keys($DB->get_records('user_password_history', array('userid' => $user2->id), 'timecreated ASC, id ASC'));
504 $this->assertSame($ids, $newids);
505 $this->assertEquals(6, $DB->count_records('user_password_history'));
509 * Test function user_add_password_history().
511 public function test_user_is_previously_used_password() {
512 global $DB;
514 $this->resetAfterTest();
516 $user1 = $this->getDataGenerator()->create_user();
517 $user2 = $this->getDataGenerator()->create_user();
518 $DB->delete_records('user_password_history', array());
520 set_config('passwordreuselimit', 0);
522 user_add_password_history($user1->id, 'pokus');
523 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus'));
525 set_config('passwordreuselimit', 3);
527 user_add_password_history($user2->id, 'pokus1');
528 user_add_password_history($user2->id, 'pokus2');
530 user_add_password_history($user1->id, 'pokus1');
531 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
532 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
533 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
534 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
536 user_add_password_history($user1->id, 'pokus2');
537 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
538 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
539 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
540 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
542 user_add_password_history($user1->id, 'pokus3');
543 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus1'));
544 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
545 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
546 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
548 user_add_password_history($user1->id, 'pokus4');
549 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
550 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus2'));
551 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
552 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
554 set_config('passwordreuselimit', 2);
556 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
557 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
558 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
559 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
561 set_config('passwordreuselimit', 3);
563 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
564 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
565 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus3'));
566 $this->assertTrue(user_is_previously_used_password($user1->id, 'pokus4'));
568 set_config('passwordreuselimit', 0);
570 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus1'));
571 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus2'));
572 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus3'));
573 $this->assertFalse(user_is_previously_used_password($user1->id, 'pokus4'));
577 * Test that password history is deleted together with user.
579 public function test_delete_of_hashes_on_user_delete() {
580 global $DB;
582 $this->resetAfterTest();
584 $user1 = $this->getDataGenerator()->create_user();
585 $user2 = $this->getDataGenerator()->create_user();
586 $DB->delete_records('user_password_history', array());
588 set_config('passwordreuselimit', 3);
590 user_add_password_history($user1->id, 'pokus');
591 user_add_password_history($user2->id, 'pokus1');
592 user_add_password_history($user2->id, 'pokus2');
594 $this->assertEquals(3, $DB->count_records('user_password_history'));
595 $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
596 $this->assertEquals(2, $DB->count_records('user_password_history', array('userid' => $user2->id)));
598 delete_user($user2);
599 $this->assertEquals(1, $DB->count_records('user_password_history'));
600 $this->assertEquals(1, $DB->count_records('user_password_history', array('userid' => $user1->id)));
601 $this->assertEquals(0, $DB->count_records('user_password_history', array('userid' => $user2->id)));
605 * Test user_list_view function
607 public function test_user_list_view() {
609 $this->resetAfterTest();
611 // Course without sections.
612 $course = $this->getDataGenerator()->create_course();
613 $context = \context_course::instance($course->id);
615 $this->setAdminUser();
617 // Redirect events to the sink, so we can recover them later.
618 $sink = $this->redirectEvents();
620 user_list_view($course, $context);
621 $events = $sink->get_events();
622 $this->assertCount(1, $events);
623 $event = reset($events);
625 // Check the event details are correct.
626 $this->assertInstanceOf('\core\event\user_list_viewed', $event);
627 $this->assertEquals($context, $event->get_context());
628 $this->assertEquals($course->shortname, $event->other['courseshortname']);
629 $this->assertEquals($course->fullname, $event->other['coursefullname']);
634 * Test setting the user menu avatar size.
636 public function test_user_menu_custom_avatar_size() {
637 global $PAGE;
638 $this->resetAfterTest(true);
640 $testsize = 100;
642 $PAGE->set_url('/');
643 $user = $this->getDataGenerator()->create_user();
644 $this->setUser($user);
645 $opts = user_get_user_navigation_info($user, $PAGE, array('avatarsize' => $testsize));
646 $avatarhtml = $opts->metadata['useravatar'];
648 $matches = [];
649 preg_match('/size-100/', $avatarhtml, $matches);
650 $this->assertCount(1, $matches);
654 * Test user_can_view_profile
656 public function test_user_can_view_profile() {
657 global $DB, $CFG;
659 $this->resetAfterTest();
661 // Create five users.
662 $user1 = $this->getDataGenerator()->create_user();
663 $user2 = $this->getDataGenerator()->create_user();
664 $user3 = $this->getDataGenerator()->create_user();
665 $user4 = $this->getDataGenerator()->create_user();
666 $user5 = $this->getDataGenerator()->create_user();
667 $user6 = $this->getDataGenerator()->create_user(array('deleted' => 1));
668 $user7 = $this->getDataGenerator()->create_user();
669 $user8 = $this->getDataGenerator()->create_user();
670 $user8->id = 0; // Visitor.
672 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
673 // Add the course creator role to the course contact and assign a user to that role.
674 $CFG->coursecontact = '2';
675 $coursecreatorrole = $DB->get_record('role', array('shortname' => 'coursecreator'));
676 $this->getDataGenerator()->role_assign($coursecreatorrole->id, $user7->id);
678 // Create two courses.
679 $course1 = $this->getDataGenerator()->create_course();
680 $course2 = $this->getDataGenerator()->create_course();
681 $coursecontext = \context_course::instance($course2->id);
682 // Prepare another course with separate groups and groupmodeforce set to true.
683 $record = new \stdClass();
684 $record->groupmode = 1;
685 $record->groupmodeforce = 1;
686 $course3 = $this->getDataGenerator()->create_course($record);
687 // Enrol users 1 and 2 in first course.
688 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
689 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
690 // Enrol users 2 and 3 in second course.
691 $this->getDataGenerator()->enrol_user($user2->id, $course2->id);
692 $this->getDataGenerator()->enrol_user($user3->id, $course2->id);
693 // Enrol users 1, 4, and 5 into course 3.
694 $this->getDataGenerator()->enrol_user($user1->id, $course3->id);
695 $this->getDataGenerator()->enrol_user($user4->id, $course3->id);
696 $this->getDataGenerator()->enrol_user($user5->id, $course3->id);
698 // 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).
699 $this->setUser($user3);
700 $this->assertFalse(user_can_view_profile($user1, $course2));
701 $this->assertFalse(user_can_view_profile($user1, $course1));
703 // Remove capability moodle/user:viewdetails in course 2.
704 assign_capability('moodle/user:viewdetails', CAP_PROHIBIT, $studentrole->id, $coursecontext);
705 // Set current user to user 1.
706 $this->setUser($user1);
707 // User 1 can see User 1's profile.
708 $this->assertTrue(user_can_view_profile($user1));
710 $tempcfg = $CFG->forceloginforprofiles;
711 $CFG->forceloginforprofiles = 0;
712 // Not forced to log in to view profiles, should be able to see all profiles besides user 6.
713 $users = array($user1, $user2, $user3, $user4, $user5, $user7);
714 foreach ($users as $user) {
715 $this->assertTrue(user_can_view_profile($user));
717 // Restore setting.
718 $CFG->forceloginforprofiles = $tempcfg;
720 // User 1 can not see user 6 as they have been deleted.
721 $this->assertFalse(user_can_view_profile($user6));
722 // User 1 can see User 7 as they are a course contact.
723 $this->assertTrue(user_can_view_profile($user7));
724 // User 1 is in a course with user 2 and has the right capability - return true.
725 $this->assertTrue(user_can_view_profile($user2));
726 // User 1 is not in a course with user 3 - return false.
727 $this->assertFalse(user_can_view_profile($user3));
729 // Set current user to user 2.
730 $this->setUser($user2);
731 // User 2 is in a course with user 3 but does not have the right capability - return false.
732 $this->assertFalse(user_can_view_profile($user3));
734 // Set user 1 in one group and users 4 and 5 in another group.
735 $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
736 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course3->id));
737 groups_add_member($group1->id, $user1->id);
738 groups_add_member($group2->id, $user4->id);
739 groups_add_member($group2->id, $user5->id);
740 $this->setUser($user1);
741 // Check that user 1 can not see user 4.
742 $this->assertFalse(user_can_view_profile($user4));
743 // Check that user 5 can see user 4.
744 $this->setUser($user5);
745 $this->assertTrue(user_can_view_profile($user4));
747 // Test the user:viewalldetails cap check using the course creator role which, by default, can't see student profiles.
748 $this->setUser($user7);
749 $this->assertFalse(user_can_view_profile($user4));
750 assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $coursecreatorrole->id, \context_system::instance()->id, true);
751 reload_all_capabilities();
752 $this->assertTrue(user_can_view_profile($user4));
753 unassign_capability('moodle/user:viewalldetails', $coursecreatorrole->id, $coursecontext->id);
754 reload_all_capabilities();
756 $CFG->coursecontact = null;
758 // Visitor (Not a guest user, userid=0).
759 $CFG->forceloginforprofiles = 1;
760 $this->setUser($user8);
761 $this->assertFalse(user_can_view_profile($user1));
763 // Let us test with guest user.
764 $this->setGuestUser();
765 $CFG->forceloginforprofiles = 1;
766 foreach ($users as $user) {
767 $this->assertFalse(user_can_view_profile($user));
770 // Even with cap, still guests should not be allowed in.
771 $guestrole = $DB->get_records_menu('role', array('shortname' => 'guest'), 'id', 'archetype, id');
772 assign_capability('moodle/user:viewdetails', CAP_ALLOW, $guestrole['guest'], \context_system::instance()->id, true);
773 reload_all_capabilities();
774 foreach ($users as $user) {
775 $this->assertFalse(user_can_view_profile($user));
778 $CFG->forceloginforprofiles = 0;
779 foreach ($users as $user) {
780 $this->assertTrue(user_can_view_profile($user));
783 // Let us test with Visitor user.
784 $this->setUser($user8);
785 $CFG->forceloginforprofiles = 1;
786 foreach ($users as $user) {
787 $this->assertFalse(user_can_view_profile($user));
790 $CFG->forceloginforprofiles = 0;
791 foreach ($users as $user) {
792 $this->assertTrue(user_can_view_profile($user));
795 // Testing non-shared courses where capabilities are met, using system role overrides.
796 $CFG->forceloginforprofiles = $tempcfg;
797 $course4 = $this->getDataGenerator()->create_course();
798 $this->getDataGenerator()->enrol_user($user1->id, $course4->id);
800 // Assign a manager role at the system context.
801 $managerrole = $DB->get_record('role', array('shortname' => 'manager'));
802 $user9 = $this->getDataGenerator()->create_user();
803 $this->getDataGenerator()->role_assign($managerrole->id, $user9->id);
805 // Make sure viewalldetails and viewdetails are overridden to 'prevent' (i.e. can be overridden at a lower context).
806 $systemcontext = \context_system::instance();
807 assign_capability('moodle/user:viewdetails', CAP_PREVENT, $managerrole->id, $systemcontext, true);
808 assign_capability('moodle/user:viewalldetails', CAP_PREVENT, $managerrole->id, $systemcontext, true);
810 // And override these to 'Allow' in a specific course.
811 $course4context = \context_course::instance($course4->id);
812 assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $managerrole->id, $course4context, true);
813 assign_capability('moodle/user:viewdetails', CAP_ALLOW, $managerrole->id, $course4context, true);
815 // The manager now shouldn't have viewdetails in the system or user context.
816 $this->setUser($user9);
817 $user1context = \context_user::instance($user1->id);
818 $this->assertFalse(has_capability('moodle/user:viewdetails', $systemcontext));
819 $this->assertFalse(has_capability('moodle/user:viewdetails', $user1context));
821 // Confirm that user_can_view_profile() returns true for $user1 when called without $course param. It should find $course1.
822 $this->assertTrue(user_can_view_profile($user1));
824 // Confirm this also works when restricting scope to just that course.
825 $this->assertTrue(user_can_view_profile($user1, $course4));
829 * Test user_get_user_details
831 public function test_user_get_user_details() {
832 global $DB;
834 $this->resetAfterTest();
836 // Create user and modify user profile.
837 $teacher = $this->getDataGenerator()->create_user();
838 $student = $this->getDataGenerator()->create_user();
839 $studentfullname = fullname($student);
841 $course1 = $this->getDataGenerator()->create_course();
842 $coursecontext = \context_course::instance($course1->id);
843 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
844 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
845 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id);
846 $this->getDataGenerator()->enrol_user($student->id, $course1->id);
847 role_assign($teacherrole->id, $teacher->id, $coursecontext->id);
848 role_assign($studentrole->id, $student->id, $coursecontext->id);
850 accesslib_clear_all_caches_for_unit_testing();
852 // Get student details as a user with super system capabilities.
853 $result = user_get_user_details($student, $course1);
854 $this->assertEquals($student->id, $result['id']);
855 $this->assertEquals($studentfullname, $result['fullname']);
856 $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
858 $this->setUser($teacher);
859 // Get student details as a user who can only see this user in a course.
860 $result = user_get_user_details($student, $course1);
861 $this->assertEquals($student->id, $result['id']);
862 $this->assertEquals($studentfullname, $result['fullname']);
863 $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
865 // Get student details with required fields.
866 $result = user_get_user_details($student, $course1, array('id', 'fullname'));
867 $this->assertCount(2, $result);
868 $this->assertEquals($student->id, $result['id']);
869 $this->assertEquals($studentfullname, $result['fullname']);
871 // Get exception for invalid required fields.
872 $this->expectException('moodle_exception');
873 $result = user_get_user_details($student, $course1, array('wrongrequiredfield'));
877 * Regression test for MDL-57840.
879 * Ensure the fields "auth, confirmed, idnumber, lang, theme, timezone and mailformat" are present when
880 * calling user_get_user_details() function.
882 public function test_user_get_user_details_missing_fields() {
883 global $CFG;
885 $this->resetAfterTest(true);
886 $this->setAdminUser(); // We need capabilities to view the data.
887 $user = self::getDataGenerator()->create_user([
888 'auth' => 'email',
889 'confirmed' => '0',
890 'idnumber' => 'someidnumber',
891 'lang' => 'en',
892 'theme' => $CFG->theme,
893 'timezone' => '5',
894 'mailformat' => '0',
895 'trackforums' => '1',
898 // Fields that should get by default.
899 $got = user_get_user_details($user);
900 self::assertSame('email', $got['auth']);
901 self::assertSame('0', $got['confirmed']);
902 self::assertSame('someidnumber', $got['idnumber']);
903 self::assertSame('en', $got['lang']);
904 self::assertSame($CFG->theme, $got['theme']);
905 self::assertSame('5', $got['timezone']);
906 self::assertSame('0', $got['mailformat']);
907 self::assertSame('1', $got['trackforums']);
911 * Test user_get_user_details_permissions.
912 * @covers ::user_get_user_details
914 public function test_user_get_user_details_permissions() {
915 global $CFG;
917 $this->resetAfterTest();
919 // Create user and modify user profile.
920 $teacher = $this->getDataGenerator()->create_user();
921 $student1 = $this->getDataGenerator()->create_user(['idnumber' => 'user1id', 'city' => 'Barcelona', 'address' => 'BCN 1B']);
922 $student2 = $this->getDataGenerator()->create_user();
923 $student1fullname = fullname($student1);
925 $course = $this->getDataGenerator()->create_course();
926 $coursecontext = \context_course::instance($course->id);
927 $this->getDataGenerator()->enrol_user($teacher->id, $course->id);
928 $this->getDataGenerator()->enrol_user($student1->id, $course->id);
929 $this->getDataGenerator()->enrol_user($student2->id, $course->id);
930 $this->getDataGenerator()->role_assign('teacher', $teacher->id, $coursecontext->id);
931 $this->getDataGenerator()->role_assign('student', $student1->id, $coursecontext->id);
932 $this->getDataGenerator()->role_assign('student', $student2->id, $coursecontext->id);
934 accesslib_clear_all_caches_for_unit_testing();
936 // Get student details as a user with super system capabilities.
937 $result = user_get_user_details($student1, $course);
938 $this->assertEquals($student1->id, $result['id']);
939 $this->assertEquals($student1fullname, $result['fullname']);
940 $this->assertEquals($course->id, $result['enrolledcourses'][0]['id']);
942 $this->setUser($student2);
944 // Get student details with required fields.
945 $result = user_get_user_details($student1, $course, array('id', 'fullname', 'timezone', 'city', 'address', 'idnumber'));
946 $this->assertCount(4, $result); // Ensure address (never returned), idnumber (identity field) are not returned here.
947 $this->assertEquals($student1->id, $result['id']);
948 $this->assertEquals($student1fullname, $result['fullname']);
949 $this->assertEquals($student1->timezone, $result['timezone']);
950 $this->assertEquals($student1->city, $result['city']);
952 // Set new identity fields and hidden fields and try to retrieve them without permission.
953 $CFG->showuseridentity = $CFG->showuseridentity . ',idnumber';
954 $CFG->hiddenuserfields = 'city';
955 $result = user_get_user_details($student1, $course, array('id', 'fullname', 'timezone', 'city', 'address', 'idnumber'));
956 $this->assertCount(3, $result); // Ensure address, city and idnumber are not returned here.
957 $this->assertEquals($student1->id, $result['id']);
958 $this->assertEquals($student1fullname, $result['fullname']);
959 $this->assertEquals($student1->timezone, $result['timezone']);
961 // Now, teacher should have permission to see the idnumber and city fields.
962 $this->setUser($teacher);
963 $result = user_get_user_details($student1, $course, array('id', 'fullname', 'timezone', 'city', 'address', 'idnumber'));
964 $this->assertCount(5, $result); // Ensure address is not returned here.
965 $this->assertEquals($student1->id, $result['id']);
966 $this->assertEquals($student1fullname, $result['fullname']);
967 $this->assertEquals($student1->timezone, $result['timezone']);
968 $this->assertEquals($student1->idnumber, $result['idnumber']);
969 $this->assertEquals($student1->city, $result['city']);
971 // And admins can see anything.
972 $this->setAdminUser();
973 $result = user_get_user_details($student1, $course, array('id', 'fullname', 'timezone', 'city', 'address', 'idnumber'));
974 $this->assertCount(6, $result);
975 $this->assertEquals($student1->id, $result['id']);
976 $this->assertEquals($student1fullname, $result['fullname']);
977 $this->assertEquals($student1->timezone, $result['timezone']);
978 $this->assertEquals($student1->idnumber, $result['idnumber']);
979 $this->assertEquals($student1->city, $result['city']);
980 $this->assertEquals($student1->address, $result['address']);
984 * Test user_get_user_details_groups.
985 * @covers ::user_get_user_details
987 public function test_user_get_user_details_groups() {
988 $this->resetAfterTest();
990 // Create user and modify user profile.
991 $teacher = $this->getDataGenerator()->create_user();
992 $student1 = $this->getDataGenerator()->create_user(['idnumber' => 'user1id', 'city' => 'Barcelona', 'address' => 'BCN 1B']);
993 $student2 = $this->getDataGenerator()->create_user();
995 $course = $this->getDataGenerator()->create_course();
996 $coursecontext = \context_course::instance($course->id);
997 $this->getDataGenerator()->enrol_user($teacher->id, $course->id);
998 $this->getDataGenerator()->enrol_user($student1->id, $course->id);
999 $this->getDataGenerator()->enrol_user($student2->id, $course->id);
1000 $this->getDataGenerator()->role_assign('teacher', $teacher->id, $coursecontext->id);
1001 $this->getDataGenerator()->role_assign('student', $student1->id, $coursecontext->id);
1002 $this->getDataGenerator()->role_assign('student', $student2->id, $coursecontext->id);
1004 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'G1']);
1005 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'G2']);
1007 // Each student in one group but teacher in two.
1008 groups_add_member($group1->id, $student1->id);
1009 groups_add_member($group1->id, $teacher->id);
1010 groups_add_member($group2->id, $student2->id);
1011 groups_add_member($group2->id, $teacher->id);
1013 accesslib_clear_all_caches_for_unit_testing();
1015 // A student can see other users groups when separate groups are not forced.
1016 $this->setUser($student2);
1018 // Get student details with groups.
1019 $result = user_get_user_details($student1, $course, array('id', 'fullname', 'groups'));
1020 $this->assertCount(3, $result);
1021 $this->assertEquals($group1->id, $result['groups'][0]['id']);
1023 // Teacher is in two different groups.
1024 $result = user_get_user_details($teacher, $course, array('id', 'fullname', 'groups'));
1026 // Order by group id.
1027 usort($result['groups'], function($a, $b) {
1028 return $a['id'] - $b['id'];
1031 $this->assertCount(3, $result);
1032 $this->assertCount(2, $result['groups']);
1033 $this->assertEquals($group1->id, $result['groups'][0]['id']);
1034 $this->assertEquals($group2->id, $result['groups'][1]['id']);
1036 // Change to separate groups.
1037 $course->groupmode = SEPARATEGROUPS;
1038 $course->groupmodeforce = true;
1039 update_course($course);
1041 // Teacher is in two groups but I can only see the one shared with me.
1042 $result = user_get_user_details($teacher, $course, array('id', 'fullname', 'groups'));
1044 $this->assertCount(3, $result);
1045 $this->assertCount(1, $result['groups']);
1046 $this->assertEquals($group2->id, $result['groups'][0]['id']);