2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
20 * Test core_user class.
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
{
32 protected function setUp(): void
{
33 $this->resetAfterTest(true);
36 public function test_get_user() {
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
));
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
));
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
));
84 * Test get_user_by_username method.
86 public function test_get_user_by_username() {
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() {
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
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
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
);
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
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);
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() {
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.
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() {
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() {
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
));
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
));
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
));
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);
486 delete_user($userexpected);
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();
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'));
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
);
523 \core_user
::get_property_definition('fullname');
524 } catch (\coding_exception
$e) {
525 $this->assertMatchesRegularExpression('/Invalid property requested./', $e->getMessage());
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);
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';
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() {
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() {
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);
750 $enuser = \core_user
::get_noreply_user();
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() {
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}&courseid={$course->id}",
896 \core_user
::get_profile_url($user, $coursecontext));
898 // Throw error if userid is invalid.
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)));
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
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();
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');
957 $this->getDataGenerator()->create_user(
959 ['picture' => 0, 'email' => 'user1@example.com'],
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
973 public static function user_name_provider(): array {
976 'user' => ['firstname' => 'first', 'lastname' => 'last'],
977 'fullnamedisplay' => 'language',
980 'simple user with lastname firstname in language settings' => [
981 'user' => ['firstname' => 'first', 'lastname' => 'last'],
982 'fullnamedisplay' => 'lastname firstname',
985 'simple user with no surname' => [
986 'user' => ['firstname' => '', 'lastname' => 'L'],
987 'fullnamedisplay' => 'language',
990 'simple user with a middle name' => [
991 'user' => ['firstname' => 'f', 'lastname' => 'l', 'middlename' => 'm'],
992 'fullnamedisplay' => 'middlename lastname',
995 'user with a middle name & fullnamedisplay contains 3 names' => [
996 'user' => ['firstname' => 'first', 'lastname' => 'last', 'middlename' => 'middle'],
997 'fullnamedisplay' => 'firstname middlename lastname',
1000 'simple user with a namefield consisting of one element' => [
1001 'user' => ['firstname' => 'first', 'lastname' => 'last'],
1002 'fullnamedisplay' => 'lastname',