MDL-48716 webservices: New ws core_user_remove_user_device
[moodle.git] / user / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * External user API
19  *
20  * @package   core_user
21  * @copyright 2009 Moodle Pty Ltd (http://moodle.com)
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
26 /**
27  * Creates a user
28  *
29  * @throws moodle_exception
30  * @param stdClass $user user to create
31  * @param bool $updatepassword if true, authentication plugin will update password.
32  * @param bool $triggerevent set false if user_created event should not be triggred.
33  *             This will not affect user_password_updated event triggering.
34  * @return int id of the newly created user
35  */
36 function user_create_user($user, $updatepassword = true, $triggerevent = true) {
37     global $CFG, $DB;
39     // Set the timecreate field to the current time.
40     if (!is_object($user)) {
41         $user = (object) $user;
42     }
44     // Check username.
45     if ($user->username !== core_text::strtolower($user->username)) {
46         throw new moodle_exception('usernamelowercase');
47     } else {
48         if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
49             throw new moodle_exception('invalidusername');
50         }
51     }
53     // Save the password in a temp value for later.
54     if ($updatepassword && isset($user->password)) {
56         // Check password toward the password policy.
57         if (!check_password_policy($user->password, $errmsg)) {
58             throw new moodle_exception($errmsg);
59         }
61         $userpassword = $user->password;
62         unset($user->password);
63     }
65     // Make sure calendartype, if set, is valid.
66     if (!empty($user->calendartype)) {
67         $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
68         if (empty($availablecalendartypes[$user->calendartype])) {
69             $user->calendartype = $CFG->calendartype;
70         }
71     } else {
72         $user->calendartype = $CFG->calendartype;
73     }
75     // Apply default values for user preferences that are stored in users table.
76     if (!isset($user->maildisplay)) {
77         $user->maildisplay = $CFG->defaultpreference_maildisplay;
78     }
79     if (!isset($user->mailformat)) {
80         $user->mailformat = $CFG->defaultpreference_mailformat;
81     }
82     if (!isset($user->maildigest)) {
83         $user->maildigest = $CFG->defaultpreference_maildigest;
84     }
85     if (!isset($user->autosubscribe)) {
86         $user->autosubscribe = $CFG->defaultpreference_autosubscribe;
87     }
88     if (!isset($user->trackforums)) {
89         $user->trackforums = $CFG->defaultpreference_trackforums;
90     }
92     $user->timecreated = time();
93     $user->timemodified = $user->timecreated;
95     // Insert the user into the database.
96     $newuserid = $DB->insert_record('user', $user);
98     // Create USER context for this user.
99     $usercontext = context_user::instance($newuserid);
101     // Update user password if necessary.
102     if (isset($userpassword)) {
103         // Get full database user row, in case auth is default.
104         $newuser = $DB->get_record('user', array('id' => $newuserid));
105         $authplugin = get_auth_plugin($newuser->auth);
106         $authplugin->user_update_password($newuser, $userpassword);
107     }
109     // Trigger event If required.
110     if ($triggerevent) {
111         \core\event\user_created::create_from_userid($newuserid)->trigger();
112     }
114     return $newuserid;
117 /**
118  * Update a user with a user object (will compare against the ID)
119  *
120  * @throws moodle_exception
121  * @param stdClass $user the user to update
122  * @param bool $updatepassword if true, authentication plugin will update password.
123  * @param bool $triggerevent set false if user_updated event should not be triggred.
124  *             This will not affect user_password_updated event triggering.
125  */
126 function user_update_user($user, $updatepassword = true, $triggerevent = true) {
127     global $DB;
129     // Set the timecreate field to the current time.
130     if (!is_object($user)) {
131         $user = (object) $user;
132     }
134     // Check username.
135     if (isset($user->username)) {
136         if ($user->username !== core_text::strtolower($user->username)) {
137             throw new moodle_exception('usernamelowercase');
138         } else {
139             if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
140                 throw new moodle_exception('invalidusername');
141             }
142         }
143     }
145     // Unset password here, for updating later, if password update is required.
146     if ($updatepassword && isset($user->password)) {
148         // Check password toward the password policy.
149         if (!check_password_policy($user->password, $errmsg)) {
150             throw new moodle_exception($errmsg);
151         }
153         $passwd = $user->password;
154         unset($user->password);
155     }
157     // Make sure calendartype, if set, is valid.
158     if (!empty($user->calendartype)) {
159         $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
160         // If it doesn't exist, then unset this value, we do not want to update the user's value.
161         if (empty($availablecalendartypes[$user->calendartype])) {
162             unset($user->calendartype);
163         }
164     } else {
165         // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
166         unset($user->calendartype);
167     }
169     $user->timemodified = time();
170     $DB->update_record('user', $user);
172     if ($updatepassword) {
173         // Get full user record.
174         $updateduser = $DB->get_record('user', array('id' => $user->id));
176         // If password was set, then update its hash.
177         if (isset($passwd)) {
178             $authplugin = get_auth_plugin($updateduser->auth);
179             if ($authplugin->can_change_password()) {
180                 $authplugin->user_update_password($updateduser, $passwd);
181             }
182         }
183     }
184     // Trigger event if required.
185     if ($triggerevent) {
186         \core\event\user_updated::create_from_userid($user->id)->trigger();
187     }
190 /**
191  * Marks user deleted in internal user database and notifies the auth plugin.
192  * Also unenrols user from all roles and does other cleanup.
193  *
194  * @todo Decide if this transaction is really needed (look for internal TODO:)
195  * @param object $user Userobject before delete    (without system magic quotes)
196  * @return boolean success
197  */
198 function user_delete_user($user) {
199     return delete_user($user);
202 /**
203  * Get users by id
204  *
205  * @param array $userids id of users to retrieve
206  * @return array
207  */
208 function user_get_users_by_id($userids) {
209     global $DB;
210     return $DB->get_records_list('user', 'id', $userids);
213 /**
214  * Returns the list of default 'displayable' fields
215  *
216  * Contains database field names but also names used to generate information, such as enrolledcourses
217  *
218  * @return array of user fields
219  */
220 function user_get_default_fields() {
221     return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
222         'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
223         'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
224         'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
225         'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
226         'groups', 'roles', 'preferences', 'enrolledcourses'
227     );
230 /**
231  *
232  * Give user record from mdl_user, build an array contains all user details.
233  *
234  * Warning: description file urls are 'webservice/pluginfile.php' is use.
235  *          it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
236  *
237  * @throws moodle_exception
238  * @param stdClass $user user record from mdl_user
239  * @param stdClass $course moodle course
240  * @param array $userfields required fields
241  * @return array|null
242  */
243 function user_get_user_details($user, $course = null, array $userfields = array()) {
244     global $USER, $DB, $CFG;
245     require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
246     require_once($CFG->dirroot . "/lib/filelib.php");      // File handling on description and friends.
248     $defaultfields = user_get_default_fields();
250     if (empty($userfields)) {
251         $userfields = $defaultfields;
252     }
254     foreach ($userfields as $thefield) {
255         if (!in_array($thefield, $defaultfields)) {
256             throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
257         }
258     }
260     // Make sure id and fullname are included.
261     if (!in_array('id', $userfields)) {
262         $userfields[] = 'id';
263     }
265     if (!in_array('fullname', $userfields)) {
266         $userfields[] = 'fullname';
267     }
269     if (!empty($course)) {
270         $context = context_course::instance($course->id);
271         $usercontext = context_user::instance($user->id);
272         $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
273     } else {
274         $context = context_user::instance($user->id);
275         $usercontext = $context;
276         $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
277     }
279     $currentuser = ($user->id == $USER->id);
280     $isadmin = is_siteadmin($USER);
282     $showuseridentityfields = get_extra_user_fields($context);
284     if (!empty($course)) {
285         $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
286     } else {
287         $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
288     }
289     $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
290     if (!empty($course)) {
291         $canviewuseremail = has_capability('moodle/course:useremail', $context);
292     } else {
293         $canviewuseremail = false;
294     }
295     $cannotviewdescription   = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
296     if (!empty($course)) {
297         $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
298     } else {
299         $canaccessallgroups = false;
300     }
302     if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
303         // Skip this user details.
304         return null;
305     }
307     $userdetails = array();
308     $userdetails['id'] = $user->id;
310     if (($isadmin or $currentuser) and in_array('username', $userfields)) {
311         $userdetails['username'] = $user->username;
312     }
313     if ($isadmin or $canviewfullnames) {
314         if (in_array('firstname', $userfields)) {
315             $userdetails['firstname'] = $user->firstname;
316         }
317         if (in_array('lastname', $userfields)) {
318             $userdetails['lastname'] = $user->lastname;
319         }
320     }
321     $userdetails['fullname'] = fullname($user);
323     if (in_array('customfields', $userfields)) {
324         $fields = $DB->get_recordset_sql("SELECT f.*
325                                             FROM {user_info_field} f
326                                             JOIN {user_info_category} c
327                                                  ON f.categoryid=c.id
328                                         ORDER BY c.sortorder ASC, f.sortorder ASC");
329         $userdetails['customfields'] = array();
330         foreach ($fields as $field) {
331             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
332             $newfield = 'profile_field_'.$field->datatype;
333             $formfield = new $newfield($field->id, $user->id);
334             if ($formfield->is_visible() and !$formfield->is_empty()) {
335                 $userdetails['customfields'][] =
336                     array('name' => $formfield->field->name, 'value' => $formfield->data,
337                         'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
338             }
339         }
340         $fields->close();
341         // Unset customfields if it's empty.
342         if (empty($userdetails['customfields'])) {
343             unset($userdetails['customfields']);
344         }
345     }
347     // Profile image.
348     if (in_array('profileimageurl', $userfields)) {
349         $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
350         $userdetails['profileimageurl'] = $profileimageurl->out(false);
351     }
352     if (in_array('profileimageurlsmall', $userfields)) {
353         $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
354         $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
355     }
357     // Hidden user field.
358     if ($canviewhiddenuserfields) {
359         $hiddenfields = array();
360         // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
361         // according to user/profile.php.
362         if ($user->address && in_array('address', $userfields)) {
363             $userdetails['address'] = $user->address;
364         }
365     } else {
366         $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
367     }
369     if ($user->phone1 && in_array('phone1', $userfields) &&
370             (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
371         $userdetails['phone1'] = $user->phone1;
372     }
373     if ($user->phone2 && in_array('phone2', $userfields) &&
374             (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
375         $userdetails['phone2'] = $user->phone2;
376     }
378     if (isset($user->description) &&
379         ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
380         if (in_array('description', $userfields)) {
381             // Always return the descriptionformat if description is requested.
382             list($userdetails['description'], $userdetails['descriptionformat']) =
383                     external_format_text($user->description, $user->descriptionformat,
384                             $usercontext->id, 'user', 'profile', null);
385         }
386     }
388     if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
389         $userdetails['country'] = $user->country;
390     }
392     if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
393         $userdetails['city'] = $user->city;
394     }
396     if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
397         $url = $user->url;
398         if (strpos($user->url, '://') === false) {
399             $url = 'http://'. $url;
400         }
401         $user->url = clean_param($user->url, PARAM_URL);
402         $userdetails['url'] = $user->url;
403     }
405     if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
406         $userdetails['icq'] = $user->icq;
407     }
409     if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
410         $userdetails['skype'] = $user->skype;
411     }
412     if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
413         $userdetails['yahoo'] = $user->yahoo;
414     }
415     if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
416         $userdetails['aim'] = $user->aim;
417     }
418     if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
419         $userdetails['msn'] = $user->msn;
420     }
422     if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
423         if ($user->firstaccess) {
424             $userdetails['firstaccess'] = $user->firstaccess;
425         } else {
426             $userdetails['firstaccess'] = 0;
427         }
428     }
429     if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
430         if ($user->lastaccess) {
431             $userdetails['lastaccess'] = $user->lastaccess;
432         } else {
433             $userdetails['lastaccess'] = 0;
434         }
435     }
437     if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
438       or $currentuser // Of course the current user is as well.
439       or $canviewuseremail  // This is a capability in course context, it will be false in usercontext.
440       or in_array('email', $showuseridentityfields)
441       or $user->maildisplay == 1
442       or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
443         $userdetails['email'] = $user->email;
444     }
446     if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
447         require_once($CFG->dirroot . '/tag/lib.php');
448         if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
449             $userdetails['interests'] = $interests;
450         }
451     }
453     // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
454     if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
455         if (in_array('idnumber', $userfields) && $user->idnumber) {
456             $userdetails['idnumber'] = $user->idnumber;
457         }
458     }
459     if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
460         if (in_array('institution', $userfields) && $user->institution) {
461             $userdetails['institution'] = $user->institution;
462         }
463     }
464     if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
465         if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
466             $userdetails['department'] = $user->department;
467         }
468     }
470     if (in_array('roles', $userfields) && !empty($course)) {
471         // Not a big secret.
472         $roles = get_user_roles($context, $user->id, false);
473         $userdetails['roles'] = array();
474         foreach ($roles as $role) {
475             $userdetails['roles'][] = array(
476                 'roleid'       => $role->roleid,
477                 'name'         => $role->name,
478                 'shortname'    => $role->shortname,
479                 'sortorder'    => $role->sortorder
480             );
481         }
482     }
484     // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
485     if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
486         $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
487                 'g.id, g.name,g.description,g.descriptionformat');
488         $userdetails['groups'] = array();
489         foreach ($usergroups as $group) {
490             list($group->description, $group->descriptionformat) =
491                 external_format_text($group->description, $group->descriptionformat,
492                         $context->id, 'group', 'description', $group->id);
493             $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
494                 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
495         }
496     }
497     // List of courses where the user is enrolled.
498     if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
499         $enrolledcourses = array();
500         if ($mycourses = enrol_get_users_courses($user->id, true)) {
501             foreach ($mycourses as $mycourse) {
502                 if ($mycourse->category) {
503                     $coursecontext = context_course::instance($mycourse->id);
504                     $enrolledcourse = array();
505                     $enrolledcourse['id'] = $mycourse->id;
506                     $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
507                     $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
508                     $enrolledcourses[] = $enrolledcourse;
509                 }
510             }
511             $userdetails['enrolledcourses'] = $enrolledcourses;
512         }
513     }
515     // User preferences.
516     if (in_array('preferences', $userfields) && $currentuser) {
517         $preferences = array();
518         $userpreferences = get_user_preferences();
519         foreach ($userpreferences as $prefname => $prefvalue) {
520             $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
521         }
522         $userdetails['preferences'] = $preferences;
523     }
525     return $userdetails;
528 /**
529  * Tries to obtain user details, either recurring directly to the user's system profile
530  * or through one of the user's course enrollments (course profile).
531  *
532  * @param stdClass $user The user.
533  * @return array if unsuccessful or the allowed user details.
534  */
535 function user_get_user_details_courses($user) {
536     global $USER;
537     $userdetails = null;
539     // Get the courses that the user is enrolled in (only active).
540     $courses = enrol_get_users_courses($user->id, true);
542     $systemprofile = false;
543     if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
544         $systemprofile = true;
545     }
547     // Try using system profile.
548     if ($systemprofile) {
549         $userdetails = user_get_user_details($user, null);
550     } else {
551         // Try through course profile.
552         foreach ($courses as $course) {
553             if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
554                 $userdetails = user_get_user_details($user, $course);
555             }
556         }
557     }
559     return $userdetails;
562 /**
563  * Check if $USER have the necessary capabilities to obtain user details.
564  *
565  * @param stdClass $user
566  * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
567  * @return bool true if $USER can view user details.
568  */
569 function can_view_user_details_cap($user, $course = null) {
570     // Check $USER has the capability to view the user details at user context.
571     $usercontext = context_user::instance($user->id);
572     $result = has_capability('moodle/user:viewdetails', $usercontext);
573     // Otherwise can $USER see them at course context.
574     if (!$result && !empty($course)) {
575         $context = context_course::instance($course->id);
576         $result = has_capability('moodle/user:viewdetails', $context);
577     }
578     return $result;
581 /**
582  * Return a list of page types
583  * @param string $pagetype current page type
584  * @param stdClass $parentcontext Block's parent context
585  * @param stdClass $currentcontext Current context of block
586  * @return array
587  */
588 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
589     return array('user-profile' => get_string('page-user-profile', 'pagetype'));
592 /**
593  * Count the number of failed login attempts for the given user, since last successful login.
594  *
595  * @param int|stdclass $user user id or object.
596  * @param bool $reset Resets failed login count, if set to true.
597  *
598  * @return int number of failed login attempts since the last successful login.
599  */
600 function user_count_login_failures($user, $reset = true) {
601     global $DB;
603     if (!is_object($user)) {
604         $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
605     }
606     if ($user->deleted) {
607         // Deleted user, nothing to do.
608         return 0;
609     }
610     $count = get_user_preferences('login_failed_count_since_success', 0, $user);
611     if ($reset) {
612         set_user_preference('login_failed_count_since_success', 0, $user);
613     }
614     return $count;
617 /**
618  * Converts a string into a flat array of menu items, where each menu items is a
619  * stdClass with fields type, url, title, pix, and imgsrc.
620  *
621  * @param string $text the menu items definition
622  * @param moodle_page $page the current page
623  * @return array
624  */
625 function user_convert_text_to_menu_items($text, $page) {
626     global $OUTPUT, $CFG;
628     $lines = explode("\n", $text);
629     $items = array();
630     $lastchild = null;
631     $lastdepth = null;
632     $lastsort = 0;
633     $children = array();
634     foreach ($lines as $line) {
635         $line = trim($line);
636         $bits = explode('|', $line, 3);
637         $itemtype = 'link';
638         if (preg_match("/^#+$/", $line)) {
639             $itemtype = 'divider';
640         } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
641             // Every item must have a name to be valid.
642             continue;
643         } else {
644             $bits[0] = ltrim($bits[0], '-');
645         }
647         // Create the child.
648         $child = new stdClass();
649         $child->itemtype = $itemtype;
650         if ($itemtype === 'divider') {
651             // Add the divider to the list of children and skip link
652             // processing.
653             $children[] = $child;
654             continue;
655         }
657         // Name processing.
658         $namebits = explode(',', $bits[0], 2);
659         if (count($namebits) == 2) {
660             // Check the validity of the identifier part of the string.
661             if (clean_param($namebits[0], PARAM_STRINGID) !== '') {
662                 // Treat this as a language string.
663                 $child->title = get_string($namebits[0], $namebits[1]);
664             }
665         }
666         if (empty($child->title)) {
667             // Use it as is, don't even clean it.
668             $child->title = $bits[0];
669         }
671         // URL processing.
672         if (!array_key_exists(1, $bits) or empty($bits[1])) {
673             // Set the url to null, and set the itemtype to invalid.
674             $bits[1] = null;
675             $child->itemtype = "invalid";
676         } else {
677             // Make sure the url is a moodle url.
678             $bits[1] = new moodle_url(trim($bits[1]));
679         }
680         $child->url = $bits[1];
682         // PIX processing.
683         $pixpath = "t/edit";
684         if (!array_key_exists(2, $bits) or empty($bits[2])) {
685             // Use the default.
686             $child->pix = $pixpath;
687         } else {
688             // Check for the specified image existing.
689             $pixpath = "t/" . $bits[2];
690             if ($page->theme->resolve_image_location($pixpath, 'moodle', true)) {
691                 // Use the image.
692                 $child->pix = $pixpath;
693             } else {
694                 // Treat it like a URL.
695                 $child->pix = null;
696                 $child->imgsrc = $bits[2];
697             }
698         }
700         // Add this child to the list of children.
701         $children[] = $child;
702     }
703     return $children;
706 /**
707  * Get a list of essential user navigation items.
708  *
709  * @param stdclass $user user object.
710  * @param moodle_page $page page object.
711  * @return stdClass $returnobj navigation information object, where:
712  *
713  *      $returnobj->navitems    array    array of links where each link is a
714  *                                       stdClass with fields url, title, and
715  *                                       pix
716  *      $returnobj->metadata    array    array of useful user metadata to be
717  *                                       used when constructing navigation;
718  *                                       fields include:
719  *
720  *          ROLE FIELDS
721  *          asotherrole    bool    whether viewing as another role
722  *          rolename       string  name of the role
723  *
724  *          USER FIELDS
725  *          These fields are for the currently-logged in user, or for
726  *          the user that the real user is currently logged in as.
727  *
728  *          userid         int        the id of the user in question
729  *          userfullname   string     the user's full name
730  *          userprofileurl moodle_url the url of the user's profile
731  *          useravatar     string     a HTML fragment - the rendered
732  *                                    user_picture for this user
733  *          userloginfail  string     an error string denoting the number
734  *                                    of login failures since last login
735  *
736  *          "REAL USER" FIELDS
737  *          These fields are for when asotheruser is true, and
738  *          correspond to the underlying "real user".
739  *
740  *          asotheruser        bool    whether viewing as another user
741  *          realuserid         int        the id of the user in question
742  *          realuserfullname   string     the user's full name
743  *          realuserprofileurl moodle_url the url of the user's profile
744  *          realuseravatar     string     a HTML fragment - the rendered
745  *                                        user_picture for this user
746  *
747  *          MNET PROVIDER FIELDS
748  *          asmnetuser            bool   whether viewing as a user from an
749  *                                       MNet provider
750  *          mnetidprovidername    string name of the MNet provider
751  *          mnetidproviderwwwroot string URL of the MNet provider
752  */
753 function user_get_user_navigation_info($user, $page) {
754     global $OUTPUT, $DB, $SESSION, $CFG;
756     $returnobject = new stdClass();
757     $returnobject->navitems = array();
758     $returnobject->metadata = array();
760     $course = $page->course;
762     // Query the environment.
763     $context = context_course::instance($course->id);
765     // Get basic user metadata.
766     $returnobject->metadata['userid'] = $user->id;
767     $returnobject->metadata['userfullname'] = fullname($user, true);
768     $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
769         'id' => $user->id
770     ));
771     $returnobject->metadata['useravatar'] = $OUTPUT->user_picture (
772         $user,
773         array(
774             'link' => false,
775             'visibletoscreenreaders' => false
776         )
777     );
778     // Build a list of items for a regular user.
780     // Query MNet status.
781     if ($returnobject->metadata['asmnetuser'] = is_mnet_remote_user($user)) {
782         $mnetidprovider = $DB->get_record('mnet_host', array('id' => $user->mnethostid));
783         $returnobject->metadata['mnetidprovidername'] = $mnetidprovider->name;
784         $returnobject->metadata['mnetidproviderwwwroot'] = $mnetidprovider->wwwroot;
785     }
787     // Did the user just log in?
788     if (isset($SESSION->justloggedin)) {
789         // Don't unset this flag as login_info still needs it.
790         if (!empty($CFG->displayloginfailures)) {
791             // We're already in /user/lib.php, so we don't need to include.
792             if ($count = user_count_login_failures($user)) {
794                 // Get login failures string.
795                 $a = new stdClass();
796                 $a->attempts = html_writer::tag('span', $count, array('class' => 'value'));
797                 $returnobject->metadata['userloginfail'] =
798                     get_string('failedloginattempts', '', $a);
800             }
801         }
802     }
804     // Links: My Home.
805     $myhome = new stdClass();
806     $myhome->itemtype = 'link';
807     $myhome->url = new moodle_url('/my/');
808     $myhome->title = get_string('mymoodle', 'admin');
809     $myhome->pix = "i/course";
810     $returnobject->navitems[] = $myhome;
812     // Links: My Profile.
813     $myprofile = new stdClass();
814     $myprofile->itemtype = 'link';
815     $myprofile->url = new moodle_url('/user/profile.php', array('id' => $user->id));
816     $myprofile->title = get_string('myprofile');
817     $myprofile->pix = "i/user";
818     $returnobject->navitems[] = $myprofile;
820     // Links: Role-return or logout link.
821     $lastobj = null;
822     $buildlogout = true;
823     $returnobject->metadata['asotherrole'] = false;
824     if (is_role_switched($course->id)) {
825         if ($role = $DB->get_record('role', array('id' => $user->access['rsw'][$context->path]))) {
826             // Build role-return link instead of logout link.
827             $rolereturn = new stdClass();
828             $rolereturn->itemtype = 'link';
829             $rolereturn->url = new moodle_url('/course/switchrole.php', array(
830                 'id' => $course->id,
831                 'sesskey' => sesskey(),
832                 'switchrole' => 0,
833                 'returnurl' => $page->url->out_as_local_url(false)
834             ));
835             $rolereturn->pix = "a/logout";
836             $rolereturn->title = get_string('switchrolereturn');
837             $lastobj = $rolereturn;
839             $returnobject->metadata['asotherrole'] = true;
840             $returnobject->metadata['rolename'] = role_get_name($role, $context);
842             $buildlogout = false;
843         }
844     }
846     if ($returnobject->metadata['asotheruser'] = \core\session\manager::is_loggedinas()) {
847         $realuser = \core\session\manager::get_realuser();
849         // Save values for the real user, as $user will be full of data for the
850         // user the user is disguised as.
851         $returnobject->metadata['realuserid'] = $realuser->id;
852         $returnobject->metadata['realuserfullname'] = fullname($realuser, true);
853         $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
854             'id' => $realuser->id
855         ));
856         $returnobject->metadata['realuseravatar'] = $OUTPUT->user_picture (
857             $realuser,
858             array(
859                 'link' => false,
860                 'visibletoscreenreaders' => false
861             )
862         );
864         // Build a user-revert link.
865         $userrevert = new stdClass();
866         $userrevert->itemtype = 'link';
867         $userrevert->url = new moodle_url('/course/loginas.php', array(
868             'id' => $course->id,
869             'sesskey' => sesskey()
870         ));
871         $userrevert->pix = "a/logout";
872         $userrevert->title = get_string('logout');
873         $lastobj = $userrevert;
875         $buildlogout = false;
876     }
878     if ($buildlogout) {
879         // Build a logout link.
880         $logout = new stdClass();
881         $logout->itemtype = 'link';
882         $logout->url = new moodle_url('/login/logout.php', array('sesskey' => sesskey()));
883         $logout->pix = "a/logout";
884         $logout->title = get_string('logout');
885         $lastobj = $logout;
886     }
888     // Before we add the last item (usually a logout link), add any
889     // custom-defined items.
890     $customitems = user_convert_text_to_menu_items($CFG->customusermenuitems, $page);
891     foreach ($customitems as $item) {
892         $returnobject->navitems[] = $item;
893     }
895     // Add the last item to the list.
896     if (!is_null($lastobj)) {
897         $returnobject->navitems[] = $lastobj;
898     }
900     return $returnobject;
903 /**
904  * Add password to the list of used hashes for this user.
905  *
906  * This is supposed to be used from:
907  *  1/ change own password form
908  *  2/ password reset process
909  *  3/ user signup in auth plugins if password changing supported
910  *
911  * @param int $userid user id
912  * @param string $password plaintext password
913  * @return void
914  */
915 function user_add_password_history($userid, $password) {
916     global $CFG, $DB;
917     require_once($CFG->libdir.'/password_compat/lib/password.php');
919     if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
920         return;
921     }
923     // Note: this is using separate code form normal password hashing because
924     //       we need to have this under control in the future. Also the auth
925     //       plugin might not store the passwords locally at all.
927     $record = new stdClass();
928     $record->userid = $userid;
929     $record->hash = password_hash($password, PASSWORD_DEFAULT);
930     $record->timecreated = time();
931     $DB->insert_record('user_password_history', $record);
933     $i = 0;
934     $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
935     foreach ($records as $record) {
936         $i++;
937         if ($i > $CFG->passwordreuselimit) {
938             $DB->delete_records('user_password_history', array('id' => $record->id));
939         }
940     }
943 /**
944  * Was this password used before on change or reset password page?
945  *
946  * The $CFG->passwordreuselimit setting determines
947  * how many times different password needs to be used
948  * before allowing previously used password again.
949  *
950  * @param int $userid user id
951  * @param string $password plaintext password
952  * @return bool true if password reused
953  */
954 function user_is_previously_used_password($userid, $password) {
955     global $CFG, $DB;
956     require_once($CFG->libdir.'/password_compat/lib/password.php');
958     if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
959         return false;
960     }
962     $reused = false;
964     $i = 0;
965     $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
966     foreach ($records as $record) {
967         $i++;
968         if ($i > $CFG->passwordreuselimit) {
969             $DB->delete_records('user_password_history', array('id' => $record->id));
970             continue;
971         }
972         // NOTE: this is slow but we cannot compare the hashes directly any more.
973         if (password_verify($password, $record->hash)) {
974             $reused = true;
975         }
976     }
978     return $reused;
981 /**
982  * Remove a user device from the Moodle database (for PUSH notifications usually).
983  *
984  * @param string $uuid The device UUID.
985  * @param string $appid The app id. If empty all the devices matching the UUID for the user will be removed.
986  * @return bool true if removed, false if the device didn't exists in the database
987  * @since Moodle 2.9
988  */
989 function user_remove_user_device($uuid, $appid = "") {
990     global $DB, $USER;
992     $conditions = array('uuid' => $uuid, 'userid' => $USER->id);
993     if (!empty($appid)) {
994         $conditions['appid'] = $appid;
995     }
997     if (!$DB->count_records('user_devices', $conditions)) {
998         return false;
999     }
1001     $DB->delete_records('user_devices', $conditions);
1003     return true;