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/>.
21 * @copyright 2009 Moodle Pty Ltd (http://moodle.com)
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 define('USER_FILTER_ENROLMENT', 1);
26 define('USER_FILTER_GROUP', 2);
27 define('USER_FILTER_LAST_ACCESS', 3);
28 define('USER_FILTER_ROLE', 4);
29 define('USER_FILTER_STATUS', 5);
30 define('USER_FILTER_STRING', 6);
35 * @throws moodle_exception
36 * @param stdClass $user user to create
37 * @param bool $updatepassword if true, authentication plugin will update password.
38 * @param bool $triggerevent set false if user_created event should not be triggred.
39 * This will not affect user_password_updated event triggering.
40 * @return int id of the newly created user
42 function user_create_user($user, $updatepassword = true, $triggerevent = true) {
45 // Set the timecreate field to the current time.
46 if (!is_object($user)) {
47 $user = (object) $user;
51 if (trim($user->username
) === '') {
52 throw new moodle_exception('invalidusernameblank');
55 if ($user->username
!== core_text
::strtolower($user->username
)) {
56 throw new moodle_exception('usernamelowercase');
59 if ($user->username
!== core_user
::clean_field($user->username
, 'username')) {
60 throw new moodle_exception('invalidusername');
63 // Save the password in a temp value for later.
64 if ($updatepassword && isset($user->password
)) {
66 // Check password toward the password policy.
67 if (!check_password_policy($user->password
, $errmsg, $user)) {
68 throw new moodle_exception($errmsg);
71 $userpassword = $user->password
;
72 unset($user->password
);
75 // Apply default values for user preferences that are stored in users table.
76 if (!isset($user->calendartype
)) {
77 $user->calendartype
= core_user
::get_property_default('calendartype');
79 if (!isset($user->maildisplay
)) {
80 $user->maildisplay
= core_user
::get_property_default('maildisplay');
82 if (!isset($user->mailformat
)) {
83 $user->mailformat
= core_user
::get_property_default('mailformat');
85 if (!isset($user->maildigest
)) {
86 $user->maildigest
= core_user
::get_property_default('maildigest');
88 if (!isset($user->autosubscribe
)) {
89 $user->autosubscribe
= core_user
::get_property_default('autosubscribe');
91 if (!isset($user->trackforums
)) {
92 $user->trackforums
= core_user
::get_property_default('trackforums');
94 if (!isset($user->lang
)) {
95 $user->lang
= core_user
::get_property_default('lang');
97 if (!isset($user->city
)) {
98 $user->city
= core_user
::get_property_default('city');
100 if (!isset($user->country
)) {
101 // The default value of $CFG->country is 0, but that isn't a valid property for the user field, so switch to ''.
102 $user->country
= core_user
::get_property_default('country') ?
: '';
105 $user->timecreated
= time();
106 $user->timemodified
= $user->timecreated
;
108 // Validate user data object.
109 $uservalidation = core_user
::validate($user);
110 if ($uservalidation !== true) {
111 foreach ($uservalidation as $field => $message) {
112 debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER
);
113 $user->$field = core_user
::clean_field($user->$field, $field);
117 // Insert the user into the database.
118 $newuserid = $DB->insert_record('user', $user);
120 // Create USER context for this user.
121 $usercontext = context_user
::instance($newuserid);
123 // Update user password if necessary.
124 if (isset($userpassword)) {
125 // Get full database user row, in case auth is default.
126 $newuser = $DB->get_record('user', array('id' => $newuserid));
127 $authplugin = get_auth_plugin($newuser->auth
);
128 $authplugin->user_update_password($newuser, $userpassword);
131 // Trigger event If required.
133 \core\event\user_created
::create_from_userid($newuserid)->trigger();
136 // Purge the associated caches for the current user only.
137 $presignupcache = \cache
::make('core', 'presignup');
138 $presignupcache->purge_current_user();
144 * Update a user with a user object (will compare against the ID)
146 * @throws moodle_exception
147 * @param stdClass $user the user to update
148 * @param bool $updatepassword if true, authentication plugin will update password.
149 * @param bool $triggerevent set false if user_updated event should not be triggred.
150 * This will not affect user_password_updated event triggering.
152 function user_update_user($user, $updatepassword = true, $triggerevent = true) {
155 // Set the timecreate field to the current time.
156 if (!is_object($user)) {
157 $user = (object) $user;
161 if (isset($user->username
)) {
162 if ($user->username
!== core_text
::strtolower($user->username
)) {
163 throw new moodle_exception('usernamelowercase');
165 if ($user->username
!== core_user
::clean_field($user->username
, 'username')) {
166 throw new moodle_exception('invalidusername');
171 // Unset password here, for updating later, if password update is required.
172 if ($updatepassword && isset($user->password
)) {
174 // Check password toward the password policy.
175 if (!check_password_policy($user->password
, $errmsg, $user)) {
176 throw new moodle_exception($errmsg);
179 $passwd = $user->password
;
180 unset($user->password
);
183 // Make sure calendartype, if set, is valid.
184 if (empty($user->calendartype
)) {
185 // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
186 unset($user->calendartype
);
189 $user->timemodified
= time();
191 // Validate user data object.
192 $uservalidation = core_user
::validate($user);
193 if ($uservalidation !== true) {
194 foreach ($uservalidation as $field => $message) {
195 debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER
);
196 $user->$field = core_user
::clean_field($user->$field, $field);
200 $DB->update_record('user', $user);
202 if ($updatepassword) {
203 // Get full user record.
204 $updateduser = $DB->get_record('user', array('id' => $user->id
));
206 // If password was set, then update its hash.
207 if (isset($passwd)) {
208 $authplugin = get_auth_plugin($updateduser->auth
);
209 if ($authplugin->can_change_password()) {
210 $authplugin->user_update_password($updateduser, $passwd);
214 // Trigger event if required.
216 \core\event\user_updated
::create_from_userid($user->id
)->trigger();
221 * Marks user deleted in internal user database and notifies the auth plugin.
222 * Also unenrols user from all roles and does other cleanup.
224 * @todo Decide if this transaction is really needed (look for internal TODO:)
225 * @param object $user Userobject before delete (without system magic quotes)
226 * @return boolean success
228 function user_delete_user($user) {
229 return delete_user($user);
235 * @param array $userids id of users to retrieve
238 function user_get_users_by_id($userids) {
240 return $DB->get_records_list('user', 'id', $userids);
244 * Returns the list of default 'displayable' fields
246 * Contains database field names but also names used to generate information, such as enrolledcourses
248 * @return array of user fields
250 function user_get_default_fields() {
251 return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
252 'address', 'phone1', 'phone2', 'department',
253 'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
254 'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
255 'city', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
256 'groups', 'roles', 'preferences', 'enrolledcourses', 'suspended', 'lastcourseaccess'
262 * Give user record from mdl_user, build an array contains all user details.
264 * Warning: description file urls are 'webservice/pluginfile.php' is use.
265 * it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
267 * @throws moodle_exception
268 * @param stdClass $user user record from mdl_user
269 * @param stdClass $course moodle course
270 * @param array $userfields required fields
273 function user_get_user_details($user, $course = null, array $userfields = array()) {
274 global $USER, $DB, $CFG, $PAGE;
275 require_once($CFG->dirroot
. "/user/profile/lib.php"); // Custom field library.
276 require_once($CFG->dirroot
. "/lib/filelib.php"); // File handling on description and friends.
278 $defaultfields = user_get_default_fields();
280 if (empty($userfields)) {
281 $userfields = $defaultfields;
284 foreach ($userfields as $thefield) {
285 if (!in_array($thefield, $defaultfields)) {
286 throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
290 // Make sure id and fullname are included.
291 if (!in_array('id', $userfields)) {
292 $userfields[] = 'id';
295 if (!in_array('fullname', $userfields)) {
296 $userfields[] = 'fullname';
299 if (!empty($course)) {
300 $context = context_course
::instance($course->id
);
301 $usercontext = context_user
::instance($user->id
);
302 $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) ||
has_capability('moodle/user:viewdetails', $usercontext));
304 $context = context_user
::instance($user->id
);
305 $usercontext = $context;
306 $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
309 $currentuser = ($user->id
== $USER->id
);
310 $isadmin = is_siteadmin($USER);
312 // This does not need to include custom profile fields as it is only used to check specific
314 $showuseridentityfields = \core_user\fields
::get_identity_fields($context, false);
316 if (!empty($course)) {
317 $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
319 $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
321 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
322 if (!empty($course)) {
323 $canviewuseremail = has_capability('moodle/course:useremail', $context);
325 $canviewuseremail = false;
327 $cannotviewdescription = !empty($CFG->profilesforenrolledusersonly
) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id
));
328 if (!empty($course)) {
329 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
331 $canaccessallgroups = false;
334 if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id
)) {
335 // Skip this user details.
339 $userdetails = array();
340 $userdetails['id'] = $user->id
;
342 if (in_array('username', $userfields)) {
343 if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
344 $userdetails['username'] = $user->username
;
347 if ($isadmin or $canviewfullnames) {
348 if (in_array('firstname', $userfields)) {
349 $userdetails['firstname'] = $user->firstname
;
351 if (in_array('lastname', $userfields)) {
352 $userdetails['lastname'] = $user->lastname
;
355 $userdetails['fullname'] = fullname($user, $canviewfullnames);
357 if (in_array('customfields', $userfields)) {
358 $categories = profile_get_user_fields_with_data_by_category($user->id
);
359 $userdetails['customfields'] = array();
360 foreach ($categories as $categoryid => $fields) {
361 foreach ($fields as $formfield) {
362 if ($formfield->is_visible() and !$formfield->is_empty()) {
364 // TODO: Part of MDL-50728, this conditional coding must be moved to
365 // proper profile fields API so they are self-contained.
366 // We only use display_data in fields that require text formatting.
367 if ($formfield->field
->datatype
== 'text' or $formfield->field
->datatype
== 'textarea') {
368 $fieldvalue = $formfield->display_data();
370 // Cases: datetime, checkbox and menu.
371 $fieldvalue = $formfield->data
;
374 $userdetails['customfields'][] =
375 array('name' => $formfield->field
->name
, 'value' => $fieldvalue,
376 'type' => $formfield->field
->datatype
, 'shortname' => $formfield->field
->shortname
);
380 // Unset customfields if it's empty.
381 if (empty($userdetails['customfields'])) {
382 unset($userdetails['customfields']);
387 if (in_array('profileimageurl', $userfields)) {
388 $userpicture = new user_picture($user);
389 $userpicture->size
= 1; // Size f1.
390 $userdetails['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
392 if (in_array('profileimageurlsmall', $userfields)) {
393 if (!isset($userpicture)) {
394 $userpicture = new user_picture($user);
396 $userpicture->size
= 0; // Size f2.
397 $userdetails['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
400 // Hidden user field.
401 if ($canviewhiddenuserfields) {
402 $hiddenfields = array();
404 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields
));
408 if (!empty($user->address
) && (in_array('address', $userfields)
409 && in_array('address', $showuseridentityfields) ||
$isadmin)) {
410 $userdetails['address'] = $user->address
;
412 if (!empty($user->phone1
) && (in_array('phone1', $userfields)
413 && in_array('phone1', $showuseridentityfields) ||
$isadmin)) {
414 $userdetails['phone1'] = $user->phone1
;
416 if (!empty($user->phone2
) && (in_array('phone2', $userfields)
417 && in_array('phone2', $showuseridentityfields) ||
$isadmin)) {
418 $userdetails['phone2'] = $user->phone2
;
421 if (isset($user->description
) &&
422 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
423 if (in_array('description', $userfields)) {
424 // Always return the descriptionformat if description is requested.
425 list($userdetails['description'], $userdetails['descriptionformat']) =
426 external_format_text($user->description
, $user->descriptionformat
,
427 $usercontext->id
, 'user', 'profile', null);
431 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country
) {
432 $userdetails['country'] = $user->country
;
435 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city
) {
436 $userdetails['city'] = $user->city
;
439 if (in_array('timezone', $userfields) && (!isset($hiddenfields['timezone']) ||
$isadmin) && $user->timezone
) {
440 $userdetails['timezone'] = $user->timezone
;
443 if (in_array('suspended', $userfields) && (!isset($hiddenfields['suspended']) or $isadmin)) {
444 $userdetails['suspended'] = (bool)$user->suspended
;
447 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
448 if ($user->firstaccess
) {
449 $userdetails['firstaccess'] = $user->firstaccess
;
451 $userdetails['firstaccess'] = 0;
454 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
455 if ($user->lastaccess
) {
456 $userdetails['lastaccess'] = $user->lastaccess
;
458 $userdetails['lastaccess'] = 0;
462 // Hidden fields restriction to lastaccess field applies to both site and course access time.
463 if (in_array('lastcourseaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
464 if (isset($user->lastcourseaccess
)) {
465 $userdetails['lastcourseaccess'] = $user->lastcourseaccess
;
467 $userdetails['lastcourseaccess'] = 0;
471 if (in_array('email', $userfields) && (
473 or (!isset($hiddenfields['email']) and (
474 $user->maildisplay
== core_user
::MAILDISPLAY_EVERYONE
475 or ($user->maildisplay
== core_user
::MAILDISPLAY_COURSE_MEMBERS_ONLY
and enrol_sharing_course($user, $USER))
476 or $canviewuseremail // TODO: Deprecate/remove for MDL-37479.
478 or in_array('email', $showuseridentityfields)
480 $userdetails['email'] = $user->email
;
483 if (in_array('interests', $userfields)) {
484 $interests = core_tag_tag
::get_item_tags_array('core', 'user', $user->id
, core_tag_tag
::BOTH_STANDARD_AND_NOT
, 0, false);
486 $userdetails['interests'] = join(', ', $interests);
490 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
491 if (in_array('idnumber', $userfields) && $user->idnumber
) {
492 if (in_array('idnumber', $showuseridentityfields) or $currentuser or
493 has_capability('moodle/user:viewalldetails', $context)) {
494 $userdetails['idnumber'] = $user->idnumber
;
497 if (in_array('institution', $userfields) && $user->institution
) {
498 if (in_array('institution', $showuseridentityfields) or $currentuser or
499 has_capability('moodle/user:viewalldetails', $context)) {
500 $userdetails['institution'] = $user->institution
;
503 // Isset because it's ok to have department 0.
504 if (in_array('department', $userfields) && isset($user->department
)) {
505 if (in_array('department', $showuseridentityfields) or $currentuser or
506 has_capability('moodle/user:viewalldetails', $context)) {
507 $userdetails['department'] = $user->department
;
511 if (in_array('roles', $userfields) && !empty($course)) {
513 $roles = get_user_roles($context, $user->id
, false);
514 $userdetails['roles'] = array();
515 foreach ($roles as $role) {
516 $userdetails['roles'][] = array(
517 'roleid' => $role->roleid
,
518 'name' => $role->name
,
519 'shortname' => $role->shortname
,
520 'sortorder' => $role->sortorder
525 // Return user groups.
526 if (in_array('groups', $userfields) && !empty($course)) {
527 if ($usergroups = groups_get_all_groups($course->id
, $user->id
)) {
528 $userdetails['groups'] = [];
529 foreach ($usergroups as $group) {
530 if ($course->groupmode
== SEPARATEGROUPS
&& !$canaccessallgroups && $user->id
!= $USER->id
) {
531 // In separate groups, I only have to see the groups shared between both users.
532 if (!groups_is_member($group->id
, $USER->id
)) {
537 $userdetails['groups'][] = [
539 'name' => format_string($group->name
),
540 'description' => format_text($group->description
, $group->descriptionformat
, ['context' => $context]),
541 'descriptionformat' => $group->descriptionformat
546 // List of courses where the user is enrolled.
547 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
548 $enrolledcourses = array();
549 if ($mycourses = enrol_get_users_courses($user->id
, true)) {
550 foreach ($mycourses as $mycourse) {
551 if ($mycourse->category
) {
552 $coursecontext = context_course
::instance($mycourse->id
);
553 $enrolledcourse = array();
554 $enrolledcourse['id'] = $mycourse->id
;
555 $enrolledcourse['fullname'] = format_string($mycourse->fullname
, true, array('context' => $coursecontext));
556 $enrolledcourse['shortname'] = format_string($mycourse->shortname
, true, array('context' => $coursecontext));
557 $enrolledcourses[] = $enrolledcourse;
560 $userdetails['enrolledcourses'] = $enrolledcourses;
565 if (in_array('preferences', $userfields) && $currentuser) {
566 $preferences = array();
567 $userpreferences = get_user_preferences();
568 foreach ($userpreferences as $prefname => $prefvalue) {
569 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
571 $userdetails['preferences'] = $preferences;
574 if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
575 $extrafields = ['auth', 'confirmed', 'lang', 'theme', 'mailformat'];
576 foreach ($extrafields as $extrafield) {
577 if (in_array($extrafield, $userfields) && isset($user->$extrafield)) {
578 $userdetails[$extrafield] = $user->$extrafield;
583 // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
584 if (isset($userdetails['lang'])) {
585 $userdetails['lang'] = clean_param($userdetails['lang'], PARAM_LANG
);
587 if (isset($userdetails['theme'])) {
588 $userdetails['theme'] = clean_param($userdetails['theme'], PARAM_THEME
);
595 * Tries to obtain user details, either recurring directly to the user's system profile
596 * or through one of the user's course enrollments (course profile).
598 * You can use the $userfields parameter to reduce the amount of a user record that is required by the method.
599 * The minimum user fields are:
602 * * all potential fullname fields
604 * @param stdClass $user The user.
605 * @param array $userfields An array of userfields to be returned, the values must be a
606 * subset of user_get_default_fields (optional)
607 * @return array if unsuccessful or the allowed user details.
609 function user_get_user_details_courses($user, array $userfields = []) {
613 $systemprofile = false;
614 if (can_view_user_details_cap($user) ||
($user->id
== $USER->id
) ||
has_coursecontact_role($user->id
)) {
615 $systemprofile = true;
618 // Try using system profile.
619 if ($systemprofile) {
620 $userdetails = user_get_user_details($user, null, $userfields);
622 // Try through course profile.
623 // Get the courses that the user is enrolled in (only active).
624 $courses = enrol_get_users_courses($user->id
, true);
625 foreach ($courses as $course) {
626 if (user_can_view_profile($user, $course)) {
627 $userdetails = user_get_user_details($user, $course, $userfields);
636 * Check if $USER have the necessary capabilities to obtain user details.
638 * @param stdClass $user
639 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
640 * @return bool true if $USER can view user details.
642 function can_view_user_details_cap($user, $course = null) {
643 // Check $USER has the capability to view the user details at user context.
644 $usercontext = context_user
::instance($user->id
);
645 $result = has_capability('moodle/user:viewdetails', $usercontext);
646 // Otherwise can $USER see them at course context.
647 if (!$result && !empty($course)) {
648 $context = context_course
::instance($course->id
);
649 $result = has_capability('moodle/user:viewdetails', $context);
655 * Return a list of page types
656 * @param string $pagetype current page type
657 * @param stdClass $parentcontext Block's parent context
658 * @param stdClass $currentcontext Current context of block
661 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
662 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
666 * Count the number of failed login attempts for the given user, since last successful login.
668 * @param int|stdclass $user user id or object.
669 * @param bool $reset Resets failed login count, if set to true.
671 * @return int number of failed login attempts since the last successful login.
673 function user_count_login_failures($user, $reset = true) {
676 if (!is_object($user)) {
677 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST
);
679 if ($user->deleted
) {
680 // Deleted user, nothing to do.
683 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
685 set_user_preference('login_failed_count_since_success', 0, $user);
691 * Converts a string into a flat array of menu items, where each menu items is a
692 * stdClass with fields type, url, title.
694 * @param string $text the menu items definition
695 * @param moodle_page $page the current page
698 function user_convert_text_to_menu_items($text, $page) {
699 global $OUTPUT, $CFG;
701 $lines = explode("\n", $text);
707 foreach ($lines as $line) {
709 $bits = explode('|', $line, 2);
711 if (preg_match("/^#+$/", $line)) {
712 $itemtype = 'divider';
713 } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
714 // Every item must have a name to be valid.
717 $bits[0] = ltrim($bits[0], '-');
721 $child = new stdClass();
722 $child->itemtype
= $itemtype;
723 if ($itemtype === 'divider') {
724 // Add the divider to the list of children and skip link
726 $children[] = $child;
731 $namebits = explode(',', $bits[0], 2);
732 if (count($namebits) == 2) {
733 // Check the validity of the identifier part of the string.
734 if (clean_param($namebits[0], PARAM_STRINGID
) !== '') {
735 // Treat this as a language string.
736 $child->title
= get_string($namebits[0], $namebits[1]);
737 $child->titleidentifier
= implode(',', $namebits);
740 if (empty($child->title
)) {
741 // Use it as is, don't even clean it.
742 $child->title
= $bits[0];
743 $child->titleidentifier
= str_replace(" ", "-", $bits[0]);
747 if (!array_key_exists(1, $bits) or empty($bits[1])) {
748 // Set the url to null, and set the itemtype to invalid.
750 $child->itemtype
= "invalid";
752 // Nasty hack to replace the grades with the direct url.
753 if (strpos($bits[1], '/grade/report/mygrades.php') !== false) {
754 $bits[1] = user_mygrades_url();
757 // Make sure the url is a moodle url.
758 $bits[1] = new moodle_url(trim($bits[1]));
760 $child->url
= $bits[1];
762 // Add this child to the list of children.
763 $children[] = $child;
769 * Get a list of essential user navigation items.
771 * @param stdclass $user user object.
772 * @param moodle_page $page page object.
773 * @param array $options associative array.
775 * - avatarsize=35 (size of avatar image)
776 * @return stdClass $returnobj navigation information object, where:
778 * $returnobj->navitems array array of links where each link is a
779 * stdClass with fields url, title, and
781 * $returnobj->metadata array array of useful user metadata to be
782 * used when constructing navigation;
786 * asotherrole bool whether viewing as another role
787 * rolename string name of the role
790 * These fields are for the currently-logged in user, or for
791 * the user that the real user is currently logged in as.
793 * userid int the id of the user in question
794 * userfullname string the user's full name
795 * userprofileurl moodle_url the url of the user's profile
796 * useravatar string a HTML fragment - the rendered
797 * user_picture for this user
798 * userloginfail string an error string denoting the number
799 * of login failures since last login
802 * These fields are for when asotheruser is true, and
803 * correspond to the underlying "real user".
805 * asotheruser bool whether viewing as another user
806 * realuserid int the id of the user in question
807 * realuserfullname string the user's full name
808 * realuserprofileurl moodle_url the url of the user's profile
809 * realuseravatar string a HTML fragment - the rendered
810 * user_picture for this user
812 * MNET PROVIDER FIELDS
813 * asmnetuser bool whether viewing as a user from an
815 * mnetidprovidername string name of the MNet provider
816 * mnetidproviderwwwroot string URL of the MNet provider
818 function user_get_user_navigation_info($user, $page, $options = array()) {
819 global $OUTPUT, $DB, $SESSION, $CFG;
821 $returnobject = new stdClass();
822 $returnobject->navitems
= array();
823 $returnobject->metadata
= array();
825 $guest = isguestuser();
826 if (!isloggedin() ||
$guest) {
827 $returnobject->unauthenticateduser
= [
829 'content' => $guest ?
'loggedinasguest' : 'loggedinnot',
832 return $returnobject;
835 $course = $page->course
;
837 // Query the environment.
838 $context = context_course
::instance($course->id
);
840 // Get basic user metadata.
841 $returnobject->metadata
['userid'] = $user->id
;
842 $returnobject->metadata
['userfullname'] = fullname($user);
843 $returnobject->metadata
['userprofileurl'] = new moodle_url('/user/profile.php', array(
847 $avataroptions = array('link' => false, 'visibletoscreenreaders' => false);
848 if (!empty($options['avatarsize'])) {
849 $avataroptions['size'] = $options['avatarsize'];
851 $returnobject->metadata
['useravatar'] = $OUTPUT->user_picture (
852 $user, $avataroptions
854 // Build a list of items for a regular user.
856 // Query MNet status.
857 if ($returnobject->metadata
['asmnetuser'] = is_mnet_remote_user($user)) {
858 $mnetidprovider = $DB->get_record('mnet_host', array('id' => $user->mnethostid
));
859 $returnobject->metadata
['mnetidprovidername'] = $mnetidprovider->name
;
860 $returnobject->metadata
['mnetidproviderwwwroot'] = $mnetidprovider->wwwroot
;
863 // Did the user just log in?
864 if (isset($SESSION->justloggedin
)) {
865 // Don't unset this flag as login_info still needs it.
866 if (!empty($CFG->displayloginfailures
)) {
867 // Don't reset the count either, as login_info() still needs it too.
868 if ($count = user_count_login_failures($user, false)) {
870 // Get login failures string.
872 $a->attempts
= html_writer
::tag('span', $count, array('class' => 'value mr-1 font-weight-bold'));
873 $returnobject->metadata
['userloginfail'] =
874 get_string('failedloginattempts', '', $a);
880 $returnobject->metadata
['asotherrole'] = false;
882 // Before we add the last items (usually a logout + switch role link), add any
883 // custom-defined items.
884 $customitems = user_convert_text_to_menu_items($CFG->customusermenuitems
, $page);
885 $custommenucount = 0;
886 foreach ($customitems as $item) {
887 $returnobject->navitems
[] = $item;
888 if ($item->itemtype
!== 'divider' && $item->itemtype
!== 'invalid') {
893 if ($custommenucount > 0) {
894 // Only add a divider if we have customusermenuitems.
895 $divider = new stdClass();
896 $divider->itemtype
= 'divider';
897 $returnobject->navitems
[] = $divider;
900 // Links: Preferences.
901 $preferences = new stdClass();
902 $preferences->itemtype
= 'link';
903 $preferences->url
= new moodle_url('/user/preferences.php');
904 $preferences->title
= get_string('preferences');
905 $preferences->titleidentifier
= 'preferences,moodle';
906 $returnobject->navitems
[] = $preferences;
909 if (is_role_switched($course->id
)) {
910 if ($role = $DB->get_record('role', array('id' => $user->access
['rsw'][$context->path
]))) {
911 // Build role-return link instead of logout link.
912 $rolereturn = new stdClass();
913 $rolereturn->itemtype
= 'link';
914 $rolereturn->url
= new moodle_url('/course/switchrole.php', array(
916 'sesskey' => sesskey(),
918 'returnurl' => $page->url
->out_as_local_url(false)
920 $rolereturn->title
= get_string('switchrolereturn');
921 $rolereturn->titleidentifier
= 'switchrolereturn,moodle';
922 $returnobject->navitems
[] = $rolereturn;
924 $returnobject->metadata
['asotherrole'] = true;
925 $returnobject->metadata
['rolename'] = role_get_name($role, $context);
929 // Build switch role link.
930 $roles = get_switchable_roles($context);
931 if (is_array($roles) && (count($roles) > 0)) {
932 $switchrole = new stdClass();
933 $switchrole->itemtype
= 'link';
934 $switchrole->url
= new moodle_url('/course/switchrole.php', array(
937 'returnurl' => $page->url
->out_as_local_url(false)
939 $switchrole->title
= get_string('switchroleto');
940 $switchrole->titleidentifier
= 'switchroleto,moodle';
941 $returnobject->navitems
[] = $switchrole;
945 if ($returnobject->metadata
['asotheruser'] = \core\session\manager
::is_loggedinas()) {
946 $realuser = \core\session\manager
::get_realuser();
948 // Save values for the real user, as $user will be full of data for the
949 // user is disguised as.
950 $returnobject->metadata
['realuserid'] = $realuser->id
;
951 $returnobject->metadata
['realuserfullname'] = fullname($realuser);
952 $returnobject->metadata
['realuserprofileurl'] = new moodle_url('/user/profile.php', [
953 'id' => $realuser->id
955 $returnobject->metadata
['realuseravatar'] = $OUTPUT->user_picture($realuser, $avataroptions);
957 // Build a user-revert link.
958 $userrevert = new stdClass();
959 $userrevert->itemtype
= 'link';
960 $userrevert->url
= new moodle_url('/course/loginas.php', [
962 'sesskey' => sesskey()
964 $userrevert->title
= get_string('logout');
965 $userrevert->titleidentifier
= 'logout,moodle';
966 $returnobject->navitems
[] = $userrevert;
968 // Build a logout link.
969 $logout = new stdClass();
970 $logout->itemtype
= 'link';
971 $logout->url
= new moodle_url('/login/logout.php', ['sesskey' => sesskey()]);
972 $logout->title
= get_string('logout');
973 $logout->titleidentifier
= 'logout,moodle';
974 $returnobject->navitems
[] = $logout;
977 return $returnobject;
981 * Add password to the list of used hashes for this user.
983 * This is supposed to be used from:
984 * 1/ change own password form
985 * 2/ password reset process
986 * 3/ user signup in auth plugins if password changing supported
988 * @param int $userid user id
989 * @param string $password plaintext password
992 function user_add_password_history($userid, $password) {
995 if (empty($CFG->passwordreuselimit
) or $CFG->passwordreuselimit
< 0) {
999 // Note: this is using separate code form normal password hashing because
1000 // we need to have this under control in the future. Also the auth
1001 // plugin might not store the passwords locally at all.
1003 $record = new stdClass();
1004 $record->userid
= $userid;
1005 $record->hash
= password_hash($password, PASSWORD_DEFAULT
);
1006 $record->timecreated
= time();
1007 $DB->insert_record('user_password_history', $record);
1010 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
1011 foreach ($records as $record) {
1013 if ($i > $CFG->passwordreuselimit
) {
1014 $DB->delete_records('user_password_history', array('id' => $record->id
));
1020 * Was this password used before on change or reset password page?
1022 * The $CFG->passwordreuselimit setting determines
1023 * how many times different password needs to be used
1024 * before allowing previously used password again.
1026 * @param int $userid user id
1027 * @param string $password plaintext password
1028 * @return bool true if password reused
1030 function user_is_previously_used_password($userid, $password) {
1033 if (empty($CFG->passwordreuselimit
) or $CFG->passwordreuselimit
< 0) {
1040 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
1041 foreach ($records as $record) {
1043 if ($i > $CFG->passwordreuselimit
) {
1044 $DB->delete_records('user_password_history', array('id' => $record->id
));
1047 // NOTE: this is slow but we cannot compare the hashes directly any more.
1048 if (password_verify($password, $record->hash
)) {
1057 * Remove a user device from the Moodle database (for PUSH notifications usually).
1059 * @param string $uuid The device UUID.
1060 * @param string $appid The app id. If empty all the devices matching the UUID for the user will be removed.
1061 * @return bool true if removed, false if the device didn't exists in the database
1064 function user_remove_user_device($uuid, $appid = "") {
1067 $conditions = array('uuid' => $uuid, 'userid' => $USER->id
);
1068 if (!empty($appid)) {
1069 $conditions['appid'] = $appid;
1072 if (!$DB->count_records('user_devices', $conditions)) {
1076 $DB->delete_records('user_devices', $conditions);
1082 * Trigger user_list_viewed event.
1084 * @param stdClass $course course object
1085 * @param stdClass $context course context object
1088 function user_list_view($course, $context) {
1090 $event = \core\event\user_list_viewed
::create(array(
1091 'objectid' => $course->id
,
1092 'courseid' => $course->id
,
1093 'context' => $context,
1095 'courseshortname' => $course->shortname
,
1096 'coursefullname' => $course->fullname
1103 * Returns the url to use for the "Grades" link in the user navigation.
1105 * @param int $userid The user's ID.
1106 * @param int $courseid The course ID if available.
1107 * @return mixed A URL to be directed to for "Grades".
1109 function user_mygrades_url($userid = null, $courseid = SITEID
) {
1112 if (isset($CFG->grade_mygrades_report
) && $CFG->grade_mygrades_report
!= 'external') {
1113 if (isset($userid) && $USER->id
!= $userid) {
1114 // Send to the gradebook report.
1115 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report
. '/index.php',
1116 array('id' => $courseid, 'userid' => $userid));
1118 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report
. '/index.php');
1120 } else if (isset($CFG->grade_mygrades_report
) && $CFG->grade_mygrades_report
== 'external'
1121 && !empty($CFG->gradereport_mygradeurl
)) {
1122 $url = $CFG->gradereport_mygradeurl
;
1124 $url = $CFG->wwwroot
;
1130 * Check if the current user has permission to view details of the supplied user.
1132 * This function supports two modes:
1133 * If the optional $course param is omitted, then this function finds all shared courses and checks whether the current user has
1134 * permission in any of them, returning true if so.
1135 * If the $course param is provided, then this function checks permissions in ONLY that course.
1137 * @param object $user The other user's details.
1138 * @param object $course if provided, only check permissions in this course.
1139 * @param context $usercontext The user context if available.
1140 * @return bool true for ability to view this user, else false.
1142 function user_can_view_profile($user, $course = null, $usercontext = null) {
1145 if ($user->deleted
) {
1149 // Do we need to be logged in?
1150 if (empty($CFG->forceloginforprofiles
)) {
1153 if (!isloggedin() ||
isguestuser()) {
1154 // User is not logged in and forceloginforprofile is set, we need to return now.
1159 // Current user can always view their profile.
1160 if ($USER->id
== $user->id
) {
1164 // Use callbacks so that (primarily) local plugins can prevent or allow profile access.
1165 $forceallow = false;
1166 $plugintypes = get_plugins_with_function('control_view_profile');
1167 foreach ($plugintypes as $plugins) {
1168 foreach ($plugins as $pluginfunction) {
1169 $result = $pluginfunction($user, $course, $usercontext);
1171 case core_user
::VIEWPROFILE_DO_NOT_PREVENT
:
1172 // If the plugin doesn't stop access, just continue to next plugin or use
1173 // default behaviour.
1175 case core_user
::VIEWPROFILE_FORCE_ALLOW
:
1176 // Record that we are definitely going to allow it (unless another plugin
1177 // returns _PREVENT).
1180 case core_user
::VIEWPROFILE_PREVENT
:
1181 // If any plugin returns PREVENT then we return false, regardless of what
1182 // other plugins said.
1191 // Course contacts have visible profiles always.
1192 if (has_coursecontact_role($user->id
)) {
1196 // If we're only checking the capabilities in the single provided course.
1197 if (isset($course)) {
1198 // Confirm that $user is enrolled in the $course we're checking.
1199 if (is_enrolled(context_course
::instance($course->id
), $user)) {
1200 $userscourses = array($course);
1203 // Else we're checking whether the current user can view $user's profile anywhere, so check user context first.
1204 if (empty($usercontext)) {
1205 $usercontext = context_user
::instance($user->id
);
1207 if (has_capability('moodle/user:viewdetails', $usercontext) ||
has_capability('moodle/user:viewalldetails', $usercontext)) {
1210 // This returns context information, so we can preload below.
1211 $userscourses = enrol_get_all_users_courses($user->id
);
1214 if (empty($userscourses)) {
1218 foreach ($userscourses as $userscourse) {
1219 context_helper
::preload_from_record($userscourse);
1220 $coursecontext = context_course
::instance($userscourse->id
);
1221 if (has_capability('moodle/user:viewdetails', $coursecontext) ||
1222 has_capability('moodle/user:viewalldetails', $coursecontext)) {
1223 if (!groups_user_groups_visible($userscourse, $user->id
)) {
1224 // Not a member of the same group.
1234 * Returns users tagged with a specified tag.
1236 * @param core_tag_tag $tag
1237 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
1238 * are displayed on the page and the per-page limit may be bigger
1239 * @param int $fromctx context id where the link was displayed, may be used by callbacks
1240 * to display items in the same context first
1241 * @param int $ctx context id where to search for records
1242 * @param bool $rec search in subcontexts as well
1243 * @param int $page 0-based number of page being displayed
1244 * @return \core_tag\output\tagindex
1246 function user_get_tagged_users($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) {
1249 if ($ctx && $ctx != context_system
::instance()->id
) {
1252 // Users can only be displayed in system context.
1253 $usercount = $tag->count_tagged_items('core', 'user',
1254 'it.deleted=:notdeleted', array('notdeleted' => 0));
1256 $perpage = $exclusivemode ?
24 : 5;
1258 $totalpages = ceil($usercount / $perpage);
1261 $userlist = $tag->get_tagged_items('core', 'user', $page * $perpage, $perpage,
1262 'it.deleted=:notdeleted', array('notdeleted' => 0));
1263 $renderer = $PAGE->get_renderer('core', 'user');
1264 $content .= $renderer->user_list($userlist, $exclusivemode);
1267 return new core_tag\output\tagindex
($tag, 'core', 'user', $content,
1268 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
1272 * Returns SQL that can be used to limit a query to a period where the user last accessed / did not access a course.
1274 * @param int $accesssince The unix timestamp to compare to users' last access
1275 * @param string $tableprefix
1276 * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
1279 function user_get_course_lastaccess_sql($accesssince = null, $tableprefix = 'ul', $haveaccessed = false) {
1280 return user_get_lastaccess_sql('timeaccess', $accesssince, $tableprefix, $haveaccessed);
1284 * Returns SQL that can be used to limit a query to a period where the user last accessed / did not access the system.
1286 * @param int $accesssince The unix timestamp to compare to users' last access
1287 * @param string $tableprefix
1288 * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
1291 function user_get_user_lastaccess_sql($accesssince = null, $tableprefix = 'u', $haveaccessed = false) {
1292 return user_get_lastaccess_sql('lastaccess', $accesssince, $tableprefix, $haveaccessed);
1296 * Returns SQL that can be used to limit a query to a period where the user last accessed or
1297 * did not access something recorded by a given table.
1299 * @param string $columnname The name of the access column to check against
1300 * @param int $accesssince The unix timestamp to compare to users' last access
1301 * @param string $tableprefix The query prefix of the table to check
1302 * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
1305 function user_get_lastaccess_sql($columnname, $accesssince, $tableprefix, $haveaccessed = false) {
1306 if (empty($accesssince)) {
1310 // Only users who have accessed since $accesssince.
1311 if ($haveaccessed) {
1312 if ($accesssince == -1) {
1313 // Include all users who have logged in at some point.
1314 $sql = "({$tableprefix}.{$columnname} IS NOT NULL AND {$tableprefix}.{$columnname} != 0)";
1316 // Users who have accessed since the specified time.
1317 $sql = "{$tableprefix}.{$columnname} IS NOT NULL AND {$tableprefix}.{$columnname} != 0
1318 AND {$tableprefix}.{$columnname} >= {$accesssince}";
1321 // Only users who have not accessed since $accesssince.
1323 if ($accesssince == -1) {
1324 // Users who have never accessed.
1325 $sql = "({$tableprefix}.{$columnname} IS NULL OR {$tableprefix}.{$columnname} = 0)";
1327 // Users who have not accessed since the specified time.
1328 $sql = "({$tableprefix}.{$columnname} IS NULL
1329 OR ({$tableprefix}.{$columnname} != 0 AND {$tableprefix}.{$columnname} < {$accesssince}))";
1337 * Callback for inplace editable API.
1339 * @param string $itemtype - Only user_roles is supported.
1340 * @param string $itemid - Courseid and userid separated by a :
1341 * @param string $newvalue - json encoded list of roleids.
1342 * @return \core\output\inplace_editable
1344 function core_user_inplace_editable($itemtype, $itemid, $newvalue) {
1345 if ($itemtype === 'user_roles') {
1346 return \core_user\output\user_roles_editable
::update($itemid, $newvalue);
1351 * Map an internal field name to a valid purpose from: "https://www.w3.org/TR/WCAG21/#input-purposes"
1353 * @param integer $userid
1354 * @param string $fieldname
1355 * @return string $purpose (empty string if there is no mapping).
1357 function user_edit_map_field_purpose($userid, $fieldname) {
1360 $currentuser = ($userid == $USER->id
) && !\core\session\manager
::is_loggedinas();
1361 // These are the fields considered valid to map and auto fill from a browser.
1362 // We do not include fields that are in a collapsed section by default because
1363 // the browser could auto-fill the field and cause a new value to be saved when
1364 // that field was never visible.
1365 $validmappings = array(
1366 'username' => 'username',
1367 'password' => 'current-password',
1368 'firstname' => 'given-name',
1369 'lastname' => 'family-name',
1370 'middlename' => 'additional-name',
1372 'country' => 'country',
1373 'lang' => 'language'
1377 // Only set a purpose when editing your own user details.
1378 if ($currentuser && isset($validmappings[$fieldname])) {
1379 $purpose = ' autocomplete="' . $validmappings[$fieldname] . '" ';
1386 * Update the users public key for the specified device and app.
1388 * @param string $uuid The device UUID.
1389 * @param string $appid The app id, usually something like com.moodle.moodlemobile.
1390 * @param string $publickey The app generated public key.
1394 function user_update_device_public_key(string $uuid, string $appid, string $publickey): bool {
1397 if (!$DB->get_record('user_devices',
1398 ['uuid' => $uuid, 'appid' => $appid, 'userid' => $USER->id
]
1403 $DB->set_field('user_devices', 'publickey', $publickey,
1404 ['uuid' => $uuid, 'appid' => $appid, 'userid' => $USER->id
]