Merge branch 'MDL-73483-master' of https://github.com/dmitriim/moodle
[moodle.git] / lib / tests / user_test.php
blob60851556cd33f0c401db6a490f936678c95d7e51
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;
19 /**
20 * Test core_user class.
22 * @covers \core_user
23 * @package core
24 * @copyright 2013 Rajesh Taneja <rajesh@moodle.com>
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 class user_test extends \advanced_testcase {
29 /**
30 * Setup test data.
32 protected function setUp(): void {
33 $this->resetAfterTest(true);
36 public function test_get_user() {
37 global $CFG;
40 // Create user and try fetach it with api.
41 $user = $this->getDataGenerator()->create_user();
42 $this->assertEquals($user, \core_user::get_user($user->id, '*', MUST_EXIST));
44 // Test noreply user.
45 $CFG->noreplyuserid = null;
46 $noreplyuser = \core_user::get_noreply_user();
47 $this->assertEquals(1, $noreplyuser->emailstop);
48 $this->assertFalse(\core_user::is_real_user($noreplyuser->id));
49 $this->assertEquals($CFG->noreplyaddress, $noreplyuser->email);
50 $this->assertEquals(get_string('noreplyname'), $noreplyuser->firstname);
52 // Set user as noreply user and make sure noreply propery is set.
53 \core_user::reset_internal_users();
54 $CFG->noreplyuserid = $user->id;
55 $noreplyuser = \core_user::get_noreply_user();
56 $this->assertEquals(1, $noreplyuser->emailstop);
57 $this->assertTrue(\core_user::is_real_user($noreplyuser->id));
59 // Test support user.
60 \core_user::reset_internal_users();
61 $CFG->supportemail = null;
62 $CFG->noreplyuserid = null;
63 $supportuser = \core_user::get_support_user();
64 $adminuser = get_admin();
65 $this->assertEquals($adminuser, $supportuser);
66 $this->assertTrue(\core_user::is_real_user($supportuser->id));
68 // When supportemail is set.
69 \core_user::reset_internal_users();
70 $CFG->supportemail = 'test@example.com';
71 $supportuser = \core_user::get_support_user();
72 $this->assertEquals(\core_user::SUPPORT_USER, $supportuser->id);
73 $this->assertFalse(\core_user::is_real_user($supportuser->id));
75 // Set user as support user and make sure noreply propery is set.
76 \core_user::reset_internal_users();
77 $CFG->supportuserid = $user->id;
78 $supportuser = \core_user::get_support_user();
79 $this->assertEquals($user, $supportuser);
80 $this->assertTrue(\core_user::is_real_user($supportuser->id));
83 /**
84 * Test get_user_by_username method.
86 public function test_get_user_by_username() {
87 $record = array();
88 $record['username'] = 'johndoe';
89 $record['email'] = 'johndoe@example.com';
90 $record['timecreated'] = time();
92 // Create a default user for the test.
93 $userexpected = $this->getDataGenerator()->create_user($record);
95 // Assert that the returned user is the espected one.
96 $this->assertEquals($userexpected, \core_user::get_user_by_username('johndoe'));
98 // Assert that a subset of fields is correctly returned.
99 $this->assertEquals((object) $record, \core_user::get_user_by_username('johndoe', 'username,email,timecreated'));
101 // Assert that a user with a different mnethostid will no be returned.
102 $this->assertFalse(\core_user::get_user_by_username('johndoe', 'username,email,timecreated', 2));
104 // Create a new user from a different host.
105 $record['mnethostid'] = 2;
106 $userexpected2 = $this->getDataGenerator()->create_user($record);
108 // Assert that the new user is returned when specified the correct mnethostid.
109 $this->assertEquals($userexpected2, \core_user::get_user_by_username('johndoe', '*', 2));
111 // Assert that a user not in the db return false.
112 $this->assertFalse(\core_user::get_user_by_username('janedoe'));
115 public function test_search() {
116 global $DB;
118 self::init_search_tests();
120 // Set up three courses for test.
121 $generator = $this->getDataGenerator();
122 $course1 = $generator->create_course();
123 $course2 = $generator->create_course();
124 $course3 = $generator->create_course();
126 // Manager user in system level.
127 $manager = $generator->create_user(['firstname' => 'Manager', 'lastname' => 'Person',
128 'email' => 'x@x.x']);
129 $systemcontext = \context_system::instance();
130 $generator->role_assign($DB->get_field('role', 'id', ['shortname' => 'manager']),
131 $manager->id, $systemcontext->id);
133 // Teachers in one and two courses.
134 $teacher1 = $generator->create_user(['firstname' => 'Alberto', 'lastname' => 'Unwin',
135 'email' => 'a.unwin@x.x']);
136 $generator->enrol_user($teacher1->id, $course1->id, 'teacher');
137 $teacher2and3 = $generator->create_user(['firstname' => 'Alexandra', 'lastname' => 'Penguin',
138 'email' => 'sillypenguin@x.x']);
139 $generator->enrol_user($teacher2and3->id, $course2->id, 'teacher');
140 $generator->enrol_user($teacher2and3->id, $course3->id, 'teacher');
142 // Students in each course and some on multiple courses.
143 $student1 = $generator->create_user(['firstname' => 'Amanda', 'lastname' => 'Hodder',
144 'email' => 'hodder_a@x.x']);
145 $generator->enrol_user($student1->id, $course1->id, 'student');
146 $student2 = $generator->create_user(['firstname' => 'Audrey', 'lastname' => 'Methuen',
147 'email' => 'audrey@x.x']);
148 $generator->enrol_user($student2->id, $course2->id, 'student');
149 $student3 = $generator->create_user(['firstname' => 'Austin', 'lastname' => 'Bloomsbury',
150 'email' => 'a.bloomsbury@x.x']);
151 $generator->enrol_user($student3->id, $course3->id, 'student');
152 $student1and2 = $generator->create_user(['firstname' => 'Augustus', 'lastname' => 'Random',
153 'email' => 'random@x.x']);
154 $generator->enrol_user($student1and2->id, $course1->id, 'student');
155 $generator->enrol_user($student1and2->id, $course2->id, 'student');
156 $studentall = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'House',
157 'email' => 'house@x.x']);
158 $generator->enrol_user($studentall->id, $course1->id, 'student');
159 $generator->enrol_user($studentall->id, $course2->id, 'student');
160 $generator->enrol_user($studentall->id, $course3->id, 'student');
162 // Special mixed user (name does not begin with A) is a teacher in one course and student
163 // in another.
164 $mixed = $generator->create_user(['firstname' => 'Xavier', 'lastname' => 'Harper',
165 'email' => 'xh1248@x.x']);
166 $generator->enrol_user($mixed->id, $course1->id, 'student');
167 $generator->enrol_user($mixed->id, $course3->id, 'teacher');
169 // As admin user, try searching for somebody at system level by first name, checking the
170 // results.
171 $this->setAdminUser();
172 $result = \core_user::search('Amelia');
173 $this->assertCount(1, $result);
175 // Check some basic fields, and test other fields are present.
176 $this->assertEquals($studentall->id, $result[0]->id);
177 $this->assertEquals('Amelia', $result[0]->firstname);
178 $this->assertEquals('House', $result[0]->lastname);
179 $this->assertEquals('house@x.x', $result[0]->email);
180 $this->assertEquals(0, $result[0]->deleted);
181 $this->assertObjectHasAttribute('firstnamephonetic', $result[0]);
182 $this->assertObjectHasAttribute('lastnamephonetic', $result[0]);
183 $this->assertObjectHasAttribute('middlename', $result[0]);
184 $this->assertObjectHasAttribute('alternatename', $result[0]);
185 $this->assertObjectHasAttribute('imagealt', $result[0]);
186 $this->assertObjectHasAttribute('username', $result[0]);
188 // Now search by lastname, both names, and partials, case-insensitive.
189 $this->assertEquals($result, \core_user::search('House'));
190 $this->assertEquals($result, \core_user::search('Amelia house'));
191 $this->assertEquals($result, \core_user::search('amelI'));
192 $this->assertEquals($result, \core_user::search('hoUs'));
193 $this->assertEquals($result, \core_user::search('Amelia H'));
195 // Admin user can also search by email (full or partial).
196 $this->assertEquals($result, \core_user::search('house@x.x'));
197 $this->assertEquals($result, \core_user::search('hOuse@'));
199 // What if we just search for A? (They all begin with A except the manager.)
200 $result = \core_user::search('a');
201 $this->assertCount(7, $result);
203 // Au gets us Audrey, Austin, and Augustus - in alphabetical order by surname.
204 $result = \core_user::search('au');
205 $this->assertCount(3, $result);
206 $this->assertEquals('Austin', $result[0]->firstname);
207 $this->assertEquals('Audrey', $result[1]->firstname);
208 $this->assertEquals('Augustus', $result[2]->firstname);
210 // But if we search within course 2 we'll get Audrey and Augustus first.
211 $course2context = \context_course::instance($course2->id);
212 $result = \core_user::search('au', $course2context);
213 $this->assertCount(3, $result);
214 $this->assertEquals('Audrey', $result[0]->firstname);
215 $this->assertEquals('Augustus', $result[1]->firstname);
216 $this->assertEquals('Austin', $result[2]->firstname);
218 // Try doing a few searches as manager - we should get the same results and can still
219 // search by email too.
220 $this->setUser($manager);
221 $result = \core_user::search('a');
222 $this->assertCount(7, $result);
223 $result = \core_user::search('au', $course2context);
224 $this->assertCount(3, $result);
225 $result = \core_user::search('house@x.x');
226 $this->assertCount(1, $result);
228 // Teacher 1. No site-level permission so can't see users outside the enrolled course.
229 $this->setUser($teacher1);
230 $result = \core_user::search('au');
231 $this->assertCount(1, $result);
232 $this->assertEquals('Augustus', $result[0]->firstname);
234 // Can still search by email for that user.
235 $result = \core_user::search('random@x.x');
236 $this->assertCount(1, $result);
238 // Search everyone - teacher can only see four users (including themself).
239 $result = \core_user::search('a');
240 $this->assertCount(4, $result);
242 // Search within course 2 - you get the same four users (which doesn't include
243 // everyone on that course) but the two on course 2 should be first.
244 $result = \core_user::search('a', $course2context);
245 $this->assertCount(4, $result);
246 $this->assertEquals('Amelia', $result[0]->firstname);
247 $this->assertEquals('Augustus', $result[1]->firstname);
249 // Other teacher.
250 $this->setUser($teacher2and3);
251 $result = \core_user::search('au');
252 $this->assertCount(3, $result);
254 $result = \core_user::search('a');
255 $this->assertCount(5, $result);
257 // Student can only see users on course 3.
258 $this->setUser($student3);
259 $result = \core_user::search('a');
260 $this->assertCount(3, $result);
262 $result = \core_user::search('au');
263 $this->assertCount(1, $result);
264 $this->assertEquals('Austin', $result[0]->firstname);
266 // Student cannot search by email.
267 $result = \core_user::search('a.bloomsbury@x.x');
268 $this->assertCount(0, $result);
270 // Student on all courses can see all the A users.
271 $this->setUser($studentall);
272 $result = \core_user::search('a');
273 $this->assertCount(7, $result);
275 // Mixed user can see users on courses 1 and 3.
276 $this->setUser($mixed);
277 $result = \core_user::search('a');
278 $this->assertCount(6, $result);
280 // Mixed user can search by email for students on course 3 but not on course 1.
281 $result = \core_user::search('hodder_a@x.x');
282 $this->assertCount(0, $result);
283 $result = \core_user::search('house@x.x');
284 $this->assertCount(1, $result);
288 * The search function had a bug where it failed if you have no identify fields (or only custom
289 * ones).
291 public function test_search_no_identity_fields(): void {
292 self::init_search_tests();
294 // Set no user identity fields.
295 set_config('showuseridentity', '');
297 // Set up course for test with teacher in.
298 $generator = $this->getDataGenerator();
299 $course = $generator->create_course();
300 $teacher = $generator->create_user(['firstname' => 'Alberto', 'lastname' => 'Unwin',
301 'email' => 'a.unwin@x.x']);
302 $generator->enrol_user($teacher->id, $course->id, 'teacher');
304 // Admin user has site-wide permissions, this uses one variant of the query.
305 $this->setAdminUser();
306 $result = \core_user::search('Al');
307 $this->assertCount(1, $result);
308 $this->assertEquals('Alberto', $result[0]->firstname);
310 // Teacher has course-wide permissions, this uses another variant.
311 $this->setUser($teacher);
312 $result = \core_user::search('Al');
313 $this->assertCount(1, $result);
314 $this->assertEquals('Alberto', $result[0]->firstname);
318 * Tests the search() function with limits on the number to return.
320 public function test_search_with_count() {
321 self::init_search_tests();
322 $generator = $this->getDataGenerator();
323 $course = $generator->create_course();
325 // Check default limit (30).
326 for ($i = 0; $i < 31; $i++) {
327 $student = $generator->create_user(['firstname' => 'Guy', 'lastname' => 'Xxx' . $i,
328 'email' => 'xxx@x.x']);
329 $generator->enrol_user($student->id, $course->id, 'student');
331 $this->setAdminUser();
332 $result = \core_user::search('Guy');
333 $this->assertCount(30, $result);
335 // Check a small limit.
336 $result = \core_user::search('Guy', null, 10);
337 $this->assertCount(10, $result);
339 // Check no limit.
340 $result = \core_user::search('Guy', null, 0);
341 $this->assertCount(31, $result);
345 * When course is in separate groups mode and user is a student, they can't see people who
346 * are not in the same group. This is checked by the user profile permission thing and not
347 * currently by the original query.
349 public function test_search_group_permissions() {
350 global $DB;
352 self::init_search_tests();
354 // Create one user to do the searching.
355 $generator = $this->getDataGenerator();
356 $course = $generator->create_course(['groupmode' => SEPARATEGROUPS]);
357 $searcher = $generator->create_user(['firstname' => 'Searchy', 'lastname' => 'Sam',
358 'email' => 'xxx@x.x']);
359 $generator->enrol_user($searcher->id, $course->id, 'student');
360 $group = $generator->create_group(['courseid' => $course->id]);
361 groups_add_member($group, $searcher);
363 // Create a large number of people so that we have to make multiple database reads.
364 $targets = [];
365 for ($i = 0; $i < 50; $i++) {
366 $student = $generator->create_user(['firstname' => 'Guy', 'lastname' => 'Xxx' . $i,
367 'email' => 'xxx@x.x']);
368 $generator->enrol_user($student->id, $course->id, 'student');
369 $targets[] = $student;
372 // The first and last people are in the same group.
373 groups_add_member($group, $targets[0]);
374 groups_add_member($group, $targets[49]);
376 // As searcher, we only find the 2 in the same group.
377 $this->setUser($searcher);
378 $result = \core_user::search('Guy');
379 $this->assertCount(2, $result);
381 // If we change the course to visible groups though, we get the max number.
382 $DB->set_field('course', 'groupmode', VISIBLEGROUPS, ['id' => $course->id]);
383 $result = \core_user::search('Guy');
384 $this->assertCount(30, $result);
388 * When course is in separate groups mode and user is a student, they can't see people who
389 * are not in the same group. This is checked by the user profile permission thing and not
390 * currently by the original query.
392 public function test_search_deleted_users() {
393 self::init_search_tests();
395 // Create one user to do the searching.
396 $generator = $this->getDataGenerator();
397 $course = $generator->create_course();
398 $searcher = $generator->create_user(['firstname' => 'Searchy', 'lastname' => 'Sam',
399 'email' => 'xxx@x.x']);
400 $generator->enrol_user($searcher->id, $course->id, 'student');
402 // Create another two users to search for.
403 $student1 = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'Aardvark']);
404 $student2 = $generator->create_user(['firstname' => 'Amelia', 'lastname' => 'Beetle']);
405 $generator->enrol_user($student1->id, $course->id, 'student');
406 $generator->enrol_user($student2->id, $course->id, 'student');
408 // As searcher, we find both users.
409 $this->setUser($searcher);
410 $result = \core_user::search('Amelia');
411 $this->assertCount(2, $result);
413 // What if one is deleted?
414 delete_user($student1);
415 $result = \core_user::search('Amelia');
416 $this->assertCount(1, $result);
417 $this->assertEquals('Beetle', $result[0]->lastname);
419 // Delete the other, for good measure.
420 delete_user($student2);
421 $result = \core_user::search('Amelia');
422 $this->assertCount(0, $result);
426 * Carries out standard setup for the search test functions.
428 protected static function init_search_tests() {
429 global $DB;
431 // For all existing users, set their name and email to something stupid so we don't
432 // accidentally find one, confusing the test counts.
433 $DB->set_field('user', 'firstname', 'Zaphod');
434 $DB->set_field('user', 'lastname', 'Beeblebrox');
435 $DB->set_field('user', 'email', 'zaphod@beeblebrox.example.org');
437 // This is the default value, but let's set it just to be certain in case it changes later.
438 // It affects what fields admin (and other users with the viewuseridentity permission) can
439 // search in addition to the name.
440 set_config('showuseridentity', 'email');
444 * Test require_active_user
446 public function test_require_active_user() {
447 global $DB;
449 // Create a default user for the test.
450 $userexpected = $this->getDataGenerator()->create_user();
452 // Simple case, all good.
453 \core_user::require_active_user($userexpected, true, true);
455 // Set user not confirmed.
456 $DB->set_field('user', 'confirmed', 0, array('id' => $userexpected->id));
457 try {
458 \core_user::require_active_user($userexpected);
459 } catch (\moodle_exception $e) {
460 $this->assertEquals('usernotconfirmed', $e->errorcode);
462 $DB->set_field('user', 'confirmed', 1, array('id' => $userexpected->id));
464 // Set nologin auth method.
465 $DB->set_field('user', 'auth', 'nologin', array('id' => $userexpected->id));
466 try {
467 \core_user::require_active_user($userexpected, false, true);
468 } catch (\moodle_exception $e) {
469 $this->assertEquals('suspended', $e->errorcode);
471 // Check no exceptions are thrown if we don't specify to check suspended.
472 \core_user::require_active_user($userexpected);
473 $DB->set_field('user', 'auth', 'manual', array('id' => $userexpected->id));
475 // Set user suspended.
476 $DB->set_field('user', 'suspended', 1, array('id' => $userexpected->id));
477 try {
478 \core_user::require_active_user($userexpected, true);
479 } catch (\moodle_exception $e) {
480 $this->assertEquals('suspended', $e->errorcode);
482 // Check no exceptions are thrown if we don't specify to check suspended.
483 \core_user::require_active_user($userexpected);
485 // Delete user.
486 delete_user($userexpected);
487 try {
488 \core_user::require_active_user($userexpected);
489 } catch (\moodle_exception $e) {
490 $this->assertEquals('userdeleted', $e->errorcode);
493 // Use a not real user.
494 $noreplyuser = \core_user::get_noreply_user();
495 try {
496 \core_user::require_active_user($noreplyuser, true);
497 } catch (\moodle_exception $e) {
498 $this->assertEquals('invaliduser', $e->errorcode);
501 // Get the guest user.
502 $guestuser = $DB->get_record('user', array('username' => 'guest'));
503 try {
504 \core_user::require_active_user($guestuser, true);
505 } catch (\moodle_exception $e) {
506 $this->assertEquals('guestsarenotallowed', $e->errorcode);
512 * Test get_property_definition() method.
514 public function test_get_property_definition() {
515 // Try to get a existing property.
516 $properties = \core_user::get_property_definition('id');
517 $this->assertEquals($properties['type'], PARAM_INT);
518 $properties = \core_user::get_property_definition('username');
519 $this->assertEquals($properties['type'], PARAM_USERNAME);
521 // Invalid property.
522 try {
523 \core_user::get_property_definition('fullname');
524 } catch (\coding_exception $e) {
525 $this->assertMatchesRegularExpression('/Invalid property requested./', $e->getMessage());
528 // Empty parameter.
529 try {
530 \core_user::get_property_definition('');
531 } catch (\coding_exception $e) {
532 $this->assertMatchesRegularExpression('/Invalid property requested./', $e->getMessage());
537 * Test validate() method.
539 public function test_validate() {
541 // Create user with just with username and firstname.
542 $record = array('username' => 's10', 'firstname' => 'Bebe Stevens');
543 $validation = \core_user::validate((object)$record);
545 // Validate the user, should return true as the user data is correct.
546 $this->assertTrue($validation);
548 // Create user with incorrect data (invalid country and theme).
549 $record = array('username' => 's1', 'firstname' => 'Eric Cartman', 'country' => 'UU', 'theme' => 'beise');
551 // Should return an array with 2 errors.
552 $validation = \core_user::validate((object)$record);
553 $this->assertArrayHasKey('country', $validation);
554 $this->assertArrayHasKey('theme', $validation);
555 $this->assertCount(2, $validation);
557 // Create user with malicious data (xss).
558 $record = array('username' => 's3', 'firstname' => 'Kyle<script>alert(1);<script> Broflovski');
560 // Should return an array with 1 error.
561 $validation = \core_user::validate((object)$record);
562 $this->assertCount(1, $validation);
563 $this->assertArrayHasKey('firstname', $validation);
567 * Test clean_data() method.
569 public function test_clean_data() {
570 $this->resetAfterTest(false);
572 $user = new \stdClass();
573 $user->firstname = 'John <script>alert(1)</script> Doe';
574 $user->username = 'john%#&~%*_doe';
575 $user->email = ' john@testing.com ';
576 $user->deleted = 'no';
577 $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
578 $usercleaned = \core_user::clean_data($user);
580 // Expected results.
581 $this->assertEquals('John alert(1) Doe', $usercleaned->firstname);
582 $this->assertEquals('john@testing.com', $usercleaned->email);
583 $this->assertEquals(0, $usercleaned->deleted);
584 $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', $user->description);
585 $this->assertEquals('john_doe', $user->username);
587 // Try to clean an invalid property (userfullname).
588 $user->userfullname = 'John Doe';
589 \core_user::clean_data($user);
590 $this->assertDebuggingCalled("The property 'userfullname' could not be cleaned.");
594 * Test clean_field() method.
596 public function test_clean_field() {
598 // Create a 'malicious' user object/
599 $user = new \stdClass();
600 $user->firstname = 'John <script>alert(1)</script> Doe';
601 $user->username = 'john%#&~%*_doe';
602 $user->email = ' john@testing.com ';
603 $user->deleted = 'no';
604 $user->description = '<b>A description <script>alert(123);</script>about myself.</b>';
605 $user->userfullname = 'John Doe';
607 // Expected results.
608 $this->assertEquals('John alert(1) Doe', \core_user::clean_field($user->firstname, 'firstname'));
609 $this->assertEquals('john_doe', \core_user::clean_field($user->username, 'username'));
610 $this->assertEquals('john@testing.com', \core_user::clean_field($user->email, 'email'));
611 $this->assertEquals(0, \core_user::clean_field($user->deleted, 'deleted'));
612 $this->assertEquals('<b>A description <script>alert(123);</script>about myself.</b>', \core_user::clean_field($user->description, 'description'));
614 // Try to clean an invalid property (fullname).
615 \core_user::clean_field($user->userfullname, 'fullname');
616 $this->assertDebuggingCalled("The property 'fullname' could not be cleaned.");
620 * Test get_property_type() method.
622 public function test_get_property_type() {
624 // Fetch valid properties and verify if the type is correct.
625 $type = \core_user::get_property_type('username');
626 $this->assertEquals(PARAM_USERNAME, $type);
627 $type = \core_user::get_property_type('email');
628 $this->assertEquals(PARAM_RAW_TRIMMED, $type);
629 $type = \core_user::get_property_type('timezone');
630 $this->assertEquals(PARAM_TIMEZONE, $type);
632 // Try to fetch type of a non-existent properties.
633 $nonexistingproperty = 'userfullname';
634 $this->expectException('coding_exception');
635 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
636 \core_user::get_property_type($nonexistingproperty);
637 $nonexistingproperty = 'mobilenumber';
638 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
639 \core_user::get_property_type($nonexistingproperty);
643 * Test get_property_null() method.
645 public function test_get_property_null() {
646 // Fetch valid properties and verify if it is NULL_ALLOWED or NULL_NOT_ALLOWED.
647 $property = \core_user::get_property_null('username');
648 $this->assertEquals(NULL_NOT_ALLOWED, $property);
649 $property = \core_user::get_property_null('password');
650 $this->assertEquals(NULL_NOT_ALLOWED, $property);
651 $property = \core_user::get_property_null('imagealt');
652 $this->assertEquals(NULL_ALLOWED, $property);
653 $property = \core_user::get_property_null('middlename');
654 $this->assertEquals(NULL_ALLOWED, $property);
656 // Try to fetch type of a non-existent properties.
657 $nonexistingproperty = 'lastnamefonetic';
658 $this->expectException('coding_exception');
659 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
660 \core_user::get_property_null($nonexistingproperty);
661 $nonexistingproperty = 'midlename';
662 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
663 \core_user::get_property_null($nonexistingproperty);
667 * Test get_property_choices() method.
669 public function test_get_property_choices() {
671 // Test against country property choices.
672 $choices = \core_user::get_property_choices('country');
673 $this->assertArrayHasKey('AU', $choices);
674 $this->assertArrayHasKey('BR', $choices);
675 $this->assertArrayNotHasKey('WW', $choices);
676 $this->assertArrayNotHasKey('TX', $choices);
678 // Test against lang property choices.
679 $choices = \core_user::get_property_choices('lang');
680 $this->assertArrayHasKey('en', $choices);
681 $this->assertArrayNotHasKey('ww', $choices);
682 $this->assertArrayNotHasKey('yy', $choices);
684 // Test against theme property choices.
685 $choices = \core_user::get_property_choices('theme');
686 $this->assertArrayHasKey('boost', $choices);
687 $this->assertArrayHasKey('classic', $choices);
688 $this->assertArrayNotHasKey('unknowntheme', $choices);
689 $this->assertArrayNotHasKey('wrongtheme', $choices);
691 // Try to fetch type of a non-existent properties.
692 $nonexistingproperty = 'language';
693 $this->expectException('coding_exception');
694 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
695 \core_user::get_property_null($nonexistingproperty);
696 $nonexistingproperty = 'coutries';
697 $this->expectExceptionMessage('Invalid property requested: ' . $nonexistingproperty);
698 \core_user::get_property_null($nonexistingproperty);
702 * Test get_property_default().
704 public function test_get_property_default() {
705 global $CFG;
706 $this->resetAfterTest();
708 $country = \core_user::get_property_default('country');
709 $this->assertEquals($CFG->country, $country);
710 set_config('country', 'AU');
711 \core_user::reset_caches();
712 $country = \core_user::get_property_default('country');
713 $this->assertEquals($CFG->country, $country);
715 $lang = \core_user::get_property_default('lang');
716 $this->assertEquals($CFG->lang, $lang);
717 set_config('lang', 'en');
718 $lang = \core_user::get_property_default('lang');
719 $this->assertEquals($CFG->lang, $lang);
721 $this->setTimezone('Europe/London', 'Pacific/Auckland');
722 \core_user::reset_caches();
723 $timezone = \core_user::get_property_default('timezone');
724 $this->assertEquals('Europe/London', $timezone);
725 $this->setTimezone('99', 'Pacific/Auckland');
726 \core_user::reset_caches();
727 $timezone = \core_user::get_property_default('timezone');
728 $this->assertEquals('Pacific/Auckland', $timezone);
730 $this->expectException(\coding_exception::class);
731 $this->expectExceptionMessage('Invalid property requested, or the property does not has a default value.');
732 \core_user::get_property_default('firstname');
736 * Ensure that the noreply user is not cached.
738 public function test_get_noreply_user() {
739 global $CFG;
741 // Create a new fake language 'xx' with the 'noreplyname'.
742 $langfolder = $CFG->dataroot . '/lang/xx';
743 check_dir_exists($langfolder);
744 $langconfig = "<?php\n\defined('MOODLE_INTERNAL') || die();";
745 file_put_contents($langfolder . '/langconfig.php', $langconfig);
746 $langconfig = "<?php\n\$string['noreplyname'] = 'XXX';";
747 file_put_contents($langfolder . '/moodle.php', $langconfig);
749 $CFG->lang='en';
750 $enuser = \core_user::get_noreply_user();
752 $CFG->lang='xx';
753 $xxuser = \core_user::get_noreply_user();
755 $this->assertNotEquals($enuser, $xxuser);
759 * Test is_real_user method.
761 public function test_is_real_user() {
762 global $CFG, $USER;
764 // Real users are real users.
765 $auser = $this->getDataGenerator()->create_user();
766 $guest = guest_user();
767 $this->assertTrue(\core_user::is_real_user($auser->id));
768 $this->assertTrue(\core_user::is_real_user($auser->id, true));
769 $this->assertTrue(\core_user::is_real_user($guest->id));
770 $this->assertTrue(\core_user::is_real_user($guest->id, true));
772 // Non-logged in users are not real users.
773 $this->assertSame(0, $USER->id, 'The non-logged in user should have an ID of 0.');
774 $this->assertFalse(\core_user::is_real_user($USER->id));
775 $this->assertFalse(\core_user::is_real_user($USER->id, true));
777 // Other types of logged in users are real users.
778 $this->setAdminUser();
779 $this->assertTrue(\core_user::is_real_user($USER->id));
780 $this->assertTrue(\core_user::is_real_user($USER->id, true));
781 $this->setGuestUser();
782 $this->assertTrue(\core_user::is_real_user($USER->id));
783 $this->assertTrue(\core_user::is_real_user($USER->id, true));
784 $this->setUser($auser);
785 $this->assertTrue(\core_user::is_real_user($USER->id));
786 $this->assertTrue(\core_user::is_real_user($USER->id, true));
788 // Fake accounts are not real users.
789 $CFG->noreplyuserid = null;
790 $this->assertFalse(\core_user::is_real_user(\core_user::get_noreply_user()->id));
791 $this->assertFalse(\core_user::is_real_user(\core_user::get_noreply_user()->id, true));
792 $CFG->supportuserid = null;
793 $CFG->supportemail = 'test@example.com';
794 $this->assertFalse(\core_user::is_real_user(\core_user::get_support_user()->id));
795 $this->assertFalse(\core_user::is_real_user(\core_user::get_support_user()->id, true));
799 * Tests for the {@see \core_user::awaiting_action()} method.
801 public function test_awaiting_action() {
802 global $CFG, $DB, $USER;
804 $guest = \core_user::get_user($CFG->siteguest);
805 $student = $this->getDataGenerator()->create_user();
806 $teacher = $this->getDataGenerator()->create_user();
807 $manager = $this->getDataGenerator()->create_user();
808 $admin = get_admin();
810 $this->getDataGenerator()->role_assign($DB->get_field('role', 'id', ['shortname' => 'manager']),
811 $manager->id, \context_system::instance()->id);
813 // Scenario: Guests required to agree to site policy.
814 $this->assertFalse(\core_user::awaiting_action($guest));
816 $CFG->sitepolicyguest = 'https://example.com';
817 $this->assertTrue(\core_user::awaiting_action($guest));
819 $guest->policyagreed = 1;
820 $this->assertFalse(\core_user::awaiting_action($guest));
822 // Scenario: Student required to fill their profile.
823 $this->assertFalse(\core_user::awaiting_action($student));
825 $student->firstname = '';
826 $this->assertTrue(\core_user::awaiting_action($student));
828 $student->firstname = 'Alice';
829 $this->assertFalse(\core_user::awaiting_action($student));
831 // Scenario: Teacher force to change their password.
832 $this->assertFalse(\core_user::awaiting_action($teacher));
834 set_user_preference('auth_forcepasswordchange', 1, $teacher);
835 $this->assertTrue(\core_user::awaiting_action($teacher));
837 unset_user_preference('auth_forcepasswordchange', $teacher);
838 $this->assertFalse(\core_user::awaiting_action($teacher));
840 // Scenario: Admins do not need to agree to the policy but others do.
841 $this->assertFalse(\core_user::awaiting_action($admin));
842 $this->assertFalse(\core_user::awaiting_action($manager));
843 $CFG->sitepolicy = 'https://example.com';
844 $this->assertFalse(\core_user::awaiting_action($admin));
845 $this->assertTrue(\core_user::awaiting_action($manager));
849 * Test for function to get user details.
851 * @covers \core_user::get_fullname
853 public function test_display_name() {
854 $this->resetAfterTest();
856 $user = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']);
857 $context = \context_system::instance();
859 // Show real name as the force names config are not set.
860 $this->assertEquals('John Doe', \core_user::get_fullname($user, $context));
862 // With override, still show real name.
863 $options = ['override' => true];
864 $this->assertEquals('John Doe', \core_user::get_fullname($user, $context, $options));
866 // Set the force names config.
867 set_config('forcefirstname', 'Bruce');
868 set_config('forcelastname', 'Simpson');
870 // Show forced names.
871 $this->assertEquals('Bruce Simpson', \core_user::get_fullname($user, $context));
873 // With override, show real name.
874 $options = ['override' => true];
875 $this->assertEquals('John Doe', \core_user::get_fullname($user, $context, $options));
879 * Test for function to get user details.
881 * @covers \core_user::get_profile_url
883 public function test_display_profile_url() {
884 $this->resetAfterTest();
886 $user = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']);
888 // Display profile url at site context.
889 $this->assertEquals("https://www.example.com/moodle/user/profile.php?id={$user->id}",
890 \core_user::get_profile_url($user)->out());
892 // Display profile url at course context.
893 $course = $this->getDataGenerator()->create_course();
894 $coursecontext = \context_course::instance($course->id);
895 $this->assertEquals("https://www.example.com/moodle/user/view.php?id={$user->id}&amp;courseid={$course->id}",
896 \core_user::get_profile_url($user, $coursecontext));
898 // Throw error if userid is invalid.
899 unset($user->id);
900 $this->expectException(\coding_exception::class);
901 $this->expectExceptionMessage('User id is required when displaying profile url.');
902 \core_user::get_profile_url($user, $coursecontext);
906 * Test for function to get user details.
908 * @covers \core_user::get_profile_picture
910 public function test_display_profile_picture() {
911 global $OUTPUT, $CFG;
912 $this->resetAfterTest();
914 $user1 = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe']);
915 $user2 = $this->getDataGenerator()->create_user(['picture' => 1]);
917 // Display profile picture.
918 $context = \context_system::instance();
919 // No image, show initials.
920 $this->assertStringContainsString("<span class=\"userinitials size-35\">JD</span></a>",
921 $OUTPUT->render(\core_user::get_profile_picture($user1, $context)));
922 // With Image.
923 $expectedimagesrc = $CFG->wwwroot . '/pluginfile.php/' . \context_user::instance($user2->id)->id .
924 '/user/icon/boost/f2?rev=1';
925 $this->assertStringContainsString($expectedimagesrc,
926 $OUTPUT->render(\core_user::get_profile_picture($user2, $context)));
928 // Display profile picture with options.
929 $options = ['size' => 50, 'includefullname' => true];
930 $this->assertStringContainsString("<span class=\"userinitials size-50\">JD</span>John Doe</a>",
931 $OUTPUT->render(\core_user::get_profile_picture($user1, $context, $options)));
933 // Display profile picture with options, no link.
934 $options = ['link' => false];
935 $this->assertEquals("<span class=\"userinitials size-35\">JD</span>",
936 $OUTPUT->render(\core_user::get_profile_picture($user1, $context, $options)));
940 * Test that user with Letter avatar respect language preference.
942 * @param array $userdata
943 * @param string $fullnameconfig
944 * @param string $expected
945 * @return void
946 * @covers \core_user::get_initials
947 * @dataProvider user_name_provider
949 public function test_get_initials(array $userdata, string $fullnameconfig, string $expected): void {
950 $this->resetAfterTest();
951 // Create a user.
952 $page = new \moodle_page();
953 $page->set_url('/user/profile.php');
954 $page->set_context(\context_system::instance());
955 $renderer = $page->get_renderer('core');
956 $user1 =
957 $this->getDataGenerator()->create_user(
958 array_merge(
959 ['picture' => 0, 'email' => 'user1@example.com'],
960 $userdata
963 set_config('fullnamedisplay', $fullnameconfig);
964 $initials = \core_user::get_initials($user1);
965 $this->assertEquals($expected, $initials);
969 * Provider of user configuration for testing initials rendering
971 * @return array[]
973 public static function user_name_provider(): array {
974 return [
975 'simple user' => [
976 'user' => ['firstname' => 'first', 'lastname' => 'last'],
977 'fullnamedisplay' => 'language',
978 'expected' => 'fl',
980 'simple user with lastname firstname in language settings' => [
981 'user' => ['firstname' => 'first', 'lastname' => 'last'],
982 'fullnamedisplay' => 'lastname firstname',
983 'expected' => 'lf',
985 'simple user with no surname' => [
986 'user' => ['firstname' => '', 'lastname' => 'L'],
987 'fullnamedisplay' => 'language',
988 'expected' => 'L',
990 'simple user with a middle name' => [
991 'user' => ['firstname' => 'f', 'lastname' => 'l', 'middlename' => 'm'],
992 'fullnamedisplay' => 'middlename lastname',
993 'expected' => 'ml',
995 'user with a middle name & fullnamedisplay contains 3 names' => [
996 'user' => ['firstname' => 'first', 'lastname' => 'last', 'middlename' => 'middle'],
997 'fullnamedisplay' => 'firstname middlename lastname',
998 'expected' => 'fl',
1000 'simple user with a namefield consisting of one element' => [
1001 'user' => ['firstname' => 'first', 'lastname' => 'last'],
1002 'fullnamedisplay' => 'lastname',
1003 'expected' => 'l',