MDL-41383 theme: filepicker zoom resiliency
[moodle.git] / user / lib.php
blobaee893b2b5242bffb9f74a4628878f11c598d953
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * External user API
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
26 /**
27 * Creates a user
29 * @throws moodle_exception
30 * @param stdClass $user user to create
31 * @param bool $updatepassword if true, authentication plugin will update password.
32 * @return int id of the newly created user
34 function user_create_user($user, $updatepassword = true) {
35 global $CFG, $DB;
37 // Set the timecreate field to the current time.
38 if (!is_object($user)) {
39 $user = (object) $user;
42 // Check username.
43 if ($user->username !== core_text::strtolower($user->username)) {
44 throw new moodle_exception('usernamelowercase');
45 } else {
46 if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
47 throw new moodle_exception('invalidusername');
51 // Save the password in a temp value for later.
52 if ($updatepassword && isset($user->password)) {
54 // Check password toward the password policy.
55 if (!check_password_policy($user->password, $errmsg)) {
56 throw new moodle_exception($errmsg);
59 $userpassword = $user->password;
60 unset($user->password);
63 // Make sure calendartype, if set, is valid.
64 if (!empty($user->calendartype)) {
65 $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
66 if (empty($availablecalendartypes[$user->calendartype])) {
67 $user->calendartype = $CFG->calendartype;
69 } else {
70 $user->calendartype = $CFG->calendartype;
73 $user->timecreated = time();
74 $user->timemodified = $user->timecreated;
76 // Insert the user into the database.
77 $newuserid = $DB->insert_record('user', $user);
79 // Create USER context for this user.
80 $usercontext = context_user::instance($newuserid);
82 // Update user password if necessary.
83 if (isset($userpassword)) {
84 // Get full database user row, in case auth is default.
85 $newuser = $DB->get_record('user', array('id' => $newuserid));
86 $authplugin = get_auth_plugin($newuser->auth);
87 $authplugin->user_update_password($newuser, $userpassword);
90 // Trigger event.
91 $event = \core\event\user_created::create(
92 array(
93 'objectid' => $newuserid,
94 'relateduserid' => $newuserid,
95 'context' => $usercontext
98 $event->trigger();
100 return $newuserid;
104 * Update a user with a user object (will compare against the ID)
106 * @throws moodle_exception
107 * @param stdClass $user the user to update
108 * @param bool $updatepassword if true, authentication plugin will update password.
110 function user_update_user($user, $updatepassword = true) {
111 global $DB;
113 // Set the timecreate field to the current time.
114 if (!is_object($user)) {
115 $user = (object) $user;
118 // Check username.
119 if (isset($user->username)) {
120 if ($user->username !== core_text::strtolower($user->username)) {
121 throw new moodle_exception('usernamelowercase');
122 } else {
123 if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
124 throw new moodle_exception('invalidusername');
129 // Unset password here, for updating later, if password update is required.
130 if ($updatepassword && isset($user->password)) {
132 // Check password toward the password policy.
133 if (!check_password_policy($user->password, $errmsg)) {
134 throw new moodle_exception($errmsg);
137 $passwd = $user->password;
138 unset($user->password);
141 // Make sure calendartype, if set, is valid.
142 if (!empty($user->calendartype)) {
143 $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
144 // If it doesn't exist, then unset this value, we do not want to update the user's value.
145 if (empty($availablecalendartypes[$user->calendartype])) {
146 unset($user->calendartype);
148 } else {
149 // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
150 unset($user->calendartype);
153 $user->timemodified = time();
154 $DB->update_record('user', $user);
156 if ($updatepassword) {
157 // Get full user record.
158 $updateduser = $DB->get_record('user', array('id' => $user->id));
160 // If password was set, then update its hash.
161 if (isset($passwd)) {
162 $authplugin = get_auth_plugin($updateduser->auth);
163 if ($authplugin->can_change_password()) {
164 $authplugin->user_update_password($updateduser, $passwd);
169 // Trigger event.
170 $event = \core\event\user_updated::create(
171 array(
172 'objectid' => $user->id,
173 'relateduserid' => $user->id,
174 'context' => context_user::instance($user->id)
177 $event->trigger();
181 * Marks user deleted in internal user database and notifies the auth plugin.
182 * Also unenrols user from all roles and does other cleanup.
184 * @todo Decide if this transaction is really needed (look for internal TODO:)
185 * @param object $user Userobject before delete (without system magic quotes)
186 * @return boolean success
188 function user_delete_user($user) {
189 return delete_user($user);
193 * Get users by id
195 * @param array $userids id of users to retrieve
196 * @return array
198 function user_get_users_by_id($userids) {
199 global $DB;
200 return $DB->get_records_list('user', 'id', $userids);
204 * Returns the list of default 'displayable' fields
206 * Contains database field names but also names used to generate information, such as enrolledcourses
208 * @return array of user fields
210 function user_get_default_fields() {
211 return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
212 'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
213 'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
214 'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
215 'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
216 'groups', 'roles', 'preferences', 'enrolledcourses'
222 * Give user record from mdl_user, build an array contains all user details.
224 * Warning: description file urls are 'webservice/pluginfile.php' is use.
225 * it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
227 * @throws moodle_exception
228 * @param stdClass $user user record from mdl_user
229 * @param stdClass $course moodle course
230 * @param array $userfields required fields
231 * @return array|null
233 function user_get_user_details($user, $course = null, array $userfields = array()) {
234 global $USER, $DB, $CFG;
235 require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
236 require_once($CFG->dirroot . "/lib/filelib.php"); // File handling on description and friends.
238 $defaultfields = user_get_default_fields();
240 if (empty($userfields)) {
241 $userfields = $defaultfields;
244 foreach ($userfields as $thefield) {
245 if (!in_array($thefield, $defaultfields)) {
246 throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
250 // Make sure id and fullname are included.
251 if (!in_array('id', $userfields)) {
252 $userfields[] = 'id';
255 if (!in_array('fullname', $userfields)) {
256 $userfields[] = 'fullname';
259 if (!empty($course)) {
260 $context = context_course::instance($course->id);
261 $usercontext = context_user::instance($user->id);
262 $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
263 } else {
264 $context = context_user::instance($user->id);
265 $usercontext = $context;
266 $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
269 $currentuser = ($user->id == $USER->id);
270 $isadmin = is_siteadmin($USER);
272 $showuseridentityfields = get_extra_user_fields($context);
274 if (!empty($course)) {
275 $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
276 } else {
277 $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
279 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
280 if (!empty($course)) {
281 $canviewuseremail = has_capability('moodle/course:useremail', $context);
282 } else {
283 $canviewuseremail = false;
285 $cannotviewdescription = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
286 if (!empty($course)) {
287 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
288 } else {
289 $canaccessallgroups = false;
292 if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
293 // Skip this user details.
294 return null;
297 $userdetails = array();
298 $userdetails['id'] = $user->id;
300 if (($isadmin or $currentuser) and in_array('username', $userfields)) {
301 $userdetails['username'] = $user->username;
303 if ($isadmin or $canviewfullnames) {
304 if (in_array('firstname', $userfields)) {
305 $userdetails['firstname'] = $user->firstname;
307 if (in_array('lastname', $userfields)) {
308 $userdetails['lastname'] = $user->lastname;
311 $userdetails['fullname'] = fullname($user);
313 if (in_array('customfields', $userfields)) {
314 $fields = $DB->get_recordset_sql("SELECT f.*
315 FROM {user_info_field} f
316 JOIN {user_info_category} c
317 ON f.categoryid=c.id
318 ORDER BY c.sortorder ASC, f.sortorder ASC");
319 $userdetails['customfields'] = array();
320 foreach ($fields as $field) {
321 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
322 $newfield = 'profile_field_'.$field->datatype;
323 $formfield = new $newfield($field->id, $user->id);
324 if ($formfield->is_visible() and !$formfield->is_empty()) {
325 $userdetails['customfields'][] =
326 array('name' => $formfield->field->name, 'value' => $formfield->data,
327 'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
330 $fields->close();
331 // Unset customfields if it's empty.
332 if (empty($userdetails['customfields'])) {
333 unset($userdetails['customfields']);
337 // Profile image.
338 if (in_array('profileimageurl', $userfields)) {
339 $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
340 $userdetails['profileimageurl'] = $profileimageurl->out(false);
342 if (in_array('profileimageurlsmall', $userfields)) {
343 $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
344 $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
347 // Hidden user field.
348 if ($canviewhiddenuserfields) {
349 $hiddenfields = array();
350 // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
351 // according to user/profile.php.
352 if ($user->address && in_array('address', $userfields)) {
353 $userdetails['address'] = $user->address;
355 } else {
356 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
359 if ($user->phone1 && in_array('phone1', $userfields) &&
360 (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
361 $userdetails['phone1'] = $user->phone1;
363 if ($user->phone2 && in_array('phone2', $userfields) &&
364 (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
365 $userdetails['phone2'] = $user->phone2;
368 if (isset($user->description) &&
369 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
370 if (in_array('description', $userfields)) {
371 // Always return the descriptionformat if description is requested.
372 list($userdetails['description'], $userdetails['descriptionformat']) =
373 external_format_text($user->description, $user->descriptionformat,
374 $usercontext->id, 'user', 'profile', null);
378 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
379 $userdetails['country'] = $user->country;
382 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
383 $userdetails['city'] = $user->city;
386 if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
387 $url = $user->url;
388 if (strpos($user->url, '://') === false) {
389 $url = 'http://'. $url;
391 $user->url = clean_param($user->url, PARAM_URL);
392 $userdetails['url'] = $user->url;
395 if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
396 $userdetails['icq'] = $user->icq;
399 if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
400 $userdetails['skype'] = $user->skype;
402 if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
403 $userdetails['yahoo'] = $user->yahoo;
405 if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
406 $userdetails['aim'] = $user->aim;
408 if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
409 $userdetails['msn'] = $user->msn;
412 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
413 if ($user->firstaccess) {
414 $userdetails['firstaccess'] = $user->firstaccess;
415 } else {
416 $userdetails['firstaccess'] = 0;
419 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
420 if ($user->lastaccess) {
421 $userdetails['lastaccess'] = $user->lastaccess;
422 } else {
423 $userdetails['lastaccess'] = 0;
427 if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
428 or $currentuser // Of course the current user is as well.
429 or $canviewuseremail // This is a capability in course context, it will be false in usercontext.
430 or in_array('email', $showuseridentityfields)
431 or $user->maildisplay == 1
432 or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
433 $userdetails['email'] = $user->email;
436 if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
437 require_once($CFG->dirroot . '/tag/lib.php');
438 if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
439 $userdetails['interests'] = $interests;
443 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
444 if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
445 if (in_array('idnumber', $userfields) && $user->idnumber) {
446 $userdetails['idnumber'] = $user->idnumber;
449 if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
450 if (in_array('institution', $userfields) && $user->institution) {
451 $userdetails['institution'] = $user->institution;
454 if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
455 if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
456 $userdetails['department'] = $user->department;
460 if (in_array('roles', $userfields) && !empty($course)) {
461 // Not a big secret.
462 $roles = get_user_roles($context, $user->id, false);
463 $userdetails['roles'] = array();
464 foreach ($roles as $role) {
465 $userdetails['roles'][] = array(
466 'roleid' => $role->roleid,
467 'name' => $role->name,
468 'shortname' => $role->shortname,
469 'sortorder' => $role->sortorder
474 // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
475 if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
476 $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
477 'g.id, g.name,g.description,g.descriptionformat');
478 $userdetails['groups'] = array();
479 foreach ($usergroups as $group) {
480 list($group->description, $group->descriptionformat) =
481 external_format_text($group->description, $group->descriptionformat,
482 $context->id, 'group', 'description', $group->id);
483 $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
484 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
487 // List of courses where the user is enrolled.
488 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
489 $enrolledcourses = array();
490 if ($mycourses = enrol_get_users_courses($user->id, true)) {
491 foreach ($mycourses as $mycourse) {
492 if ($mycourse->category) {
493 $coursecontext = context_course::instance($mycourse->id);
494 $enrolledcourse = array();
495 $enrolledcourse['id'] = $mycourse->id;
496 $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
497 $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
498 $enrolledcourses[] = $enrolledcourse;
501 $userdetails['enrolledcourses'] = $enrolledcourses;
505 // User preferences.
506 if (in_array('preferences', $userfields) && $currentuser) {
507 $preferences = array();
508 $userpreferences = get_user_preferences();
509 foreach ($userpreferences as $prefname => $prefvalue) {
510 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
512 $userdetails['preferences'] = $preferences;
515 return $userdetails;
519 * Tries to obtain user details, either recurring directly to the user's system profile
520 * or through one of the user's course enrollments (course profile).
522 * @param stdClass $user The user.
523 * @return array if unsuccessful or the allowed user details.
525 function user_get_user_details_courses($user) {
526 global $USER;
527 $userdetails = null;
529 // Get the courses that the user is enrolled in (only active).
530 $courses = enrol_get_users_courses($user->id, true);
532 $systemprofile = false;
533 if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
534 $systemprofile = true;
537 // Try using system profile.
538 if ($systemprofile) {
539 $userdetails = user_get_user_details($user, null);
540 } else {
541 // Try through course profile.
542 foreach ($courses as $course) {
543 if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
544 $userdetails = user_get_user_details($user, $course);
549 return $userdetails;
553 * Check if $USER have the necessary capabilities to obtain user details.
555 * @param stdClass $user
556 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
557 * @return bool true if $USER can view user details.
559 function can_view_user_details_cap($user, $course = null) {
560 // Check $USER has the capability to view the user details at user context.
561 $usercontext = context_user::instance($user->id);
562 $result = has_capability('moodle/user:viewdetails', $usercontext);
563 // Otherwise can $USER see them at course context.
564 if (!$result && !empty($course)) {
565 $context = context_course::instance($course->id);
566 $result = has_capability('moodle/user:viewdetails', $context);
568 return $result;
572 * Return a list of page types
573 * @param string $pagetype current page type
574 * @param stdClass $parentcontext Block's parent context
575 * @param stdClass $currentcontext Current context of block
576 * @return array
578 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
579 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
583 * Count the number of failed login attempts for the given user, since last successful login.
585 * @param int|stdclass $user user id or object.
586 * @param bool $reset Resets failed login count, if set to true.
588 * @return int number of failed login attempts since the last successful login.
590 function user_count_login_failures($user, $reset = true) {
591 global $DB;
593 if (!is_object($user)) {
594 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
596 if ($user->deleted) {
597 // Deleted user, nothing to do.
598 return 0;
600 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
601 if ($reset) {
602 set_user_preference('login_failed_count_since_success', 0, $user);
604 return $count;