Merge branch 'MDL-47990_27' of git://github.com/timhunt/moodle into MOODLE_27_STABLE
[moodle.git] / user / lib.php
blob1089e05760ebed6f538aafe445ee3f4e0bf08a48
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 * @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
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;
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');
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);
61 $userpassword = $user->password;
62 unset($user->password);
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;
71 } else {
72 $user->calendartype = $CFG->calendartype;
75 $user->timecreated = time();
76 $user->timemodified = $user->timecreated;
78 // Insert the user into the database.
79 $newuserid = $DB->insert_record('user', $user);
81 // Create USER context for this user.
82 $usercontext = context_user::instance($newuserid);
84 // Update user password if necessary.
85 if (isset($userpassword)) {
86 // Get full database user row, in case auth is default.
87 $newuser = $DB->get_record('user', array('id' => $newuserid));
88 $authplugin = get_auth_plugin($newuser->auth);
89 $authplugin->user_update_password($newuser, $userpassword);
92 // Trigger event If required.
93 if ($triggerevent) {
94 \core\event\user_created::create_from_userid($newuserid)->trigger();
97 return $newuserid;
101 * Update a user with a user object (will compare against the ID)
103 * @throws moodle_exception
104 * @param stdClass $user the user to update
105 * @param bool $updatepassword if true, authentication plugin will update password.
106 * @param bool $triggerevent set false if user_updated event should not be triggred.
107 * This will not affect user_password_updated event triggering.
109 function user_update_user($user, $updatepassword = true, $triggerevent = true) {
110 global $DB;
112 // Set the timecreate field to the current time.
113 if (!is_object($user)) {
114 $user = (object) $user;
117 // Check username.
118 if (isset($user->username)) {
119 if ($user->username !== core_text::strtolower($user->username)) {
120 throw new moodle_exception('usernamelowercase');
121 } else {
122 if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
123 throw new moodle_exception('invalidusername');
128 // Unset password here, for updating later, if password update is required.
129 if ($updatepassword && isset($user->password)) {
131 // Check password toward the password policy.
132 if (!check_password_policy($user->password, $errmsg)) {
133 throw new moodle_exception($errmsg);
136 $passwd = $user->password;
137 unset($user->password);
140 // Make sure calendartype, if set, is valid.
141 if (!empty($user->calendartype)) {
142 $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
143 // If it doesn't exist, then unset this value, we do not want to update the user's value.
144 if (empty($availablecalendartypes[$user->calendartype])) {
145 unset($user->calendartype);
147 } else {
148 // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
149 unset($user->calendartype);
152 $user->timemodified = time();
153 $DB->update_record('user', $user);
155 if ($updatepassword) {
156 // Get full user record.
157 $updateduser = $DB->get_record('user', array('id' => $user->id));
159 // If password was set, then update its hash.
160 if (isset($passwd)) {
161 $authplugin = get_auth_plugin($updateduser->auth);
162 if ($authplugin->can_change_password()) {
163 $authplugin->user_update_password($updateduser, $passwd);
167 // Trigger event if required.
168 if ($triggerevent) {
169 \core\event\user_updated::create_from_userid($user->id)->trigger();
174 * Marks user deleted in internal user database and notifies the auth plugin.
175 * Also unenrols user from all roles and does other cleanup.
177 * @todo Decide if this transaction is really needed (look for internal TODO:)
178 * @param object $user Userobject before delete (without system magic quotes)
179 * @return boolean success
181 function user_delete_user($user) {
182 return delete_user($user);
186 * Get users by id
188 * @param array $userids id of users to retrieve
189 * @return array
191 function user_get_users_by_id($userids) {
192 global $DB;
193 return $DB->get_records_list('user', 'id', $userids);
197 * Returns the list of default 'displayable' fields
199 * Contains database field names but also names used to generate information, such as enrolledcourses
201 * @return array of user fields
203 function user_get_default_fields() {
204 return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
205 'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
206 'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
207 'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
208 'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
209 'groups', 'roles', 'preferences', 'enrolledcourses'
215 * Give user record from mdl_user, build an array contains all user details.
217 * Warning: description file urls are 'webservice/pluginfile.php' is use.
218 * it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
220 * @throws moodle_exception
221 * @param stdClass $user user record from mdl_user
222 * @param stdClass $course moodle course
223 * @param array $userfields required fields
224 * @return array|null
226 function user_get_user_details($user, $course = null, array $userfields = array()) {
227 global $USER, $DB, $CFG;
228 require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
229 require_once($CFG->dirroot . "/lib/filelib.php"); // File handling on description and friends.
231 $defaultfields = user_get_default_fields();
233 if (empty($userfields)) {
234 $userfields = $defaultfields;
237 foreach ($userfields as $thefield) {
238 if (!in_array($thefield, $defaultfields)) {
239 throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
243 // Make sure id and fullname are included.
244 if (!in_array('id', $userfields)) {
245 $userfields[] = 'id';
248 if (!in_array('fullname', $userfields)) {
249 $userfields[] = 'fullname';
252 if (!empty($course)) {
253 $context = context_course::instance($course->id);
254 $usercontext = context_user::instance($user->id);
255 $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
256 } else {
257 $context = context_user::instance($user->id);
258 $usercontext = $context;
259 $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
262 $currentuser = ($user->id == $USER->id);
263 $isadmin = is_siteadmin($USER);
265 $showuseridentityfields = get_extra_user_fields($context);
267 if (!empty($course)) {
268 $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
269 } else {
270 $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
272 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
273 if (!empty($course)) {
274 $canviewuseremail = has_capability('moodle/course:useremail', $context);
275 } else {
276 $canviewuseremail = false;
278 $cannotviewdescription = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
279 if (!empty($course)) {
280 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
281 } else {
282 $canaccessallgroups = false;
285 if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
286 // Skip this user details.
287 return null;
290 $userdetails = array();
291 $userdetails['id'] = $user->id;
293 if (($isadmin or $currentuser) and in_array('username', $userfields)) {
294 $userdetails['username'] = $user->username;
296 if ($isadmin or $canviewfullnames) {
297 if (in_array('firstname', $userfields)) {
298 $userdetails['firstname'] = $user->firstname;
300 if (in_array('lastname', $userfields)) {
301 $userdetails['lastname'] = $user->lastname;
304 $userdetails['fullname'] = fullname($user);
306 if (in_array('customfields', $userfields)) {
307 $fields = $DB->get_recordset_sql("SELECT f.*
308 FROM {user_info_field} f
309 JOIN {user_info_category} c
310 ON f.categoryid=c.id
311 ORDER BY c.sortorder ASC, f.sortorder ASC");
312 $userdetails['customfields'] = array();
313 foreach ($fields as $field) {
314 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
315 $newfield = 'profile_field_'.$field->datatype;
316 $formfield = new $newfield($field->id, $user->id);
317 if ($formfield->is_visible() and !$formfield->is_empty()) {
318 $userdetails['customfields'][] =
319 array('name' => $formfield->field->name, 'value' => $formfield->data,
320 'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
323 $fields->close();
324 // Unset customfields if it's empty.
325 if (empty($userdetails['customfields'])) {
326 unset($userdetails['customfields']);
330 // Profile image.
331 if (in_array('profileimageurl', $userfields)) {
332 $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
333 $userdetails['profileimageurl'] = $profileimageurl->out(false);
335 if (in_array('profileimageurlsmall', $userfields)) {
336 $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
337 $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
340 // Hidden user field.
341 if ($canviewhiddenuserfields) {
342 $hiddenfields = array();
343 // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
344 // according to user/profile.php.
345 if ($user->address && in_array('address', $userfields)) {
346 $userdetails['address'] = $user->address;
348 } else {
349 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
352 if ($user->phone1 && in_array('phone1', $userfields) &&
353 (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
354 $userdetails['phone1'] = $user->phone1;
356 if ($user->phone2 && in_array('phone2', $userfields) &&
357 (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
358 $userdetails['phone2'] = $user->phone2;
361 if (isset($user->description) &&
362 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
363 if (in_array('description', $userfields)) {
364 // Always return the descriptionformat if description is requested.
365 list($userdetails['description'], $userdetails['descriptionformat']) =
366 external_format_text($user->description, $user->descriptionformat,
367 $usercontext->id, 'user', 'profile', null);
371 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
372 $userdetails['country'] = $user->country;
375 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
376 $userdetails['city'] = $user->city;
379 if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
380 $url = $user->url;
381 if (strpos($user->url, '://') === false) {
382 $url = 'http://'. $url;
384 $user->url = clean_param($user->url, PARAM_URL);
385 $userdetails['url'] = $user->url;
388 if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
389 $userdetails['icq'] = $user->icq;
392 if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
393 $userdetails['skype'] = $user->skype;
395 if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
396 $userdetails['yahoo'] = $user->yahoo;
398 if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
399 $userdetails['aim'] = $user->aim;
401 if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
402 $userdetails['msn'] = $user->msn;
405 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
406 if ($user->firstaccess) {
407 $userdetails['firstaccess'] = $user->firstaccess;
408 } else {
409 $userdetails['firstaccess'] = 0;
412 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
413 if ($user->lastaccess) {
414 $userdetails['lastaccess'] = $user->lastaccess;
415 } else {
416 $userdetails['lastaccess'] = 0;
420 if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
421 or $currentuser // Of course the current user is as well.
422 or $canviewuseremail // This is a capability in course context, it will be false in usercontext.
423 or in_array('email', $showuseridentityfields)
424 or $user->maildisplay == 1
425 or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
426 $userdetails['email'] = $user->email;
429 if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
430 require_once($CFG->dirroot . '/tag/lib.php');
431 if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
432 $userdetails['interests'] = $interests;
436 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
437 if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
438 if (in_array('idnumber', $userfields) && $user->idnumber) {
439 $userdetails['idnumber'] = $user->idnumber;
442 if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
443 if (in_array('institution', $userfields) && $user->institution) {
444 $userdetails['institution'] = $user->institution;
447 if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
448 if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
449 $userdetails['department'] = $user->department;
453 if (in_array('roles', $userfields) && !empty($course)) {
454 // Not a big secret.
455 $roles = get_user_roles($context, $user->id, false);
456 $userdetails['roles'] = array();
457 foreach ($roles as $role) {
458 $userdetails['roles'][] = array(
459 'roleid' => $role->roleid,
460 'name' => $role->name,
461 'shortname' => $role->shortname,
462 'sortorder' => $role->sortorder
467 // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
468 if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
469 $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
470 'g.id, g.name,g.description,g.descriptionformat');
471 $userdetails['groups'] = array();
472 foreach ($usergroups as $group) {
473 list($group->description, $group->descriptionformat) =
474 external_format_text($group->description, $group->descriptionformat,
475 $context->id, 'group', 'description', $group->id);
476 $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
477 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
480 // List of courses where the user is enrolled.
481 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
482 $enrolledcourses = array();
483 if ($mycourses = enrol_get_users_courses($user->id, true)) {
484 foreach ($mycourses as $mycourse) {
485 if ($mycourse->category) {
486 $coursecontext = context_course::instance($mycourse->id);
487 $enrolledcourse = array();
488 $enrolledcourse['id'] = $mycourse->id;
489 $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
490 $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
491 $enrolledcourses[] = $enrolledcourse;
494 $userdetails['enrolledcourses'] = $enrolledcourses;
498 // User preferences.
499 if (in_array('preferences', $userfields) && $currentuser) {
500 $preferences = array();
501 $userpreferences = get_user_preferences();
502 foreach ($userpreferences as $prefname => $prefvalue) {
503 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
505 $userdetails['preferences'] = $preferences;
508 return $userdetails;
512 * Tries to obtain user details, either recurring directly to the user's system profile
513 * or through one of the user's course enrollments (course profile).
515 * @param stdClass $user The user.
516 * @return array if unsuccessful or the allowed user details.
518 function user_get_user_details_courses($user) {
519 global $USER;
520 $userdetails = null;
522 // Get the courses that the user is enrolled in (only active).
523 $courses = enrol_get_users_courses($user->id, true);
525 $systemprofile = false;
526 if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
527 $systemprofile = true;
530 // Try using system profile.
531 if ($systemprofile) {
532 $userdetails = user_get_user_details($user, null);
533 } else {
534 // Try through course profile.
535 foreach ($courses as $course) {
536 if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
537 $userdetails = user_get_user_details($user, $course);
542 return $userdetails;
546 * Check if $USER have the necessary capabilities to obtain user details.
548 * @param stdClass $user
549 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
550 * @return bool true if $USER can view user details.
552 function can_view_user_details_cap($user, $course = null) {
553 // Check $USER has the capability to view the user details at user context.
554 $usercontext = context_user::instance($user->id);
555 $result = has_capability('moodle/user:viewdetails', $usercontext);
556 // Otherwise can $USER see them at course context.
557 if (!$result && !empty($course)) {
558 $context = context_course::instance($course->id);
559 $result = has_capability('moodle/user:viewdetails', $context);
561 return $result;
565 * Return a list of page types
566 * @param string $pagetype current page type
567 * @param stdClass $parentcontext Block's parent context
568 * @param stdClass $currentcontext Current context of block
569 * @return array
571 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
572 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
576 * Count the number of failed login attempts for the given user, since last successful login.
578 * @param int|stdclass $user user id or object.
579 * @param bool $reset Resets failed login count, if set to true.
581 * @return int number of failed login attempts since the last successful login.
583 function user_count_login_failures($user, $reset = true) {
584 global $DB;
586 if (!is_object($user)) {
587 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
589 if ($user->deleted) {
590 // Deleted user, nothing to do.
591 return 0;
593 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
594 if ($reset) {
595 set_user_preference('login_failed_count_since_success', 0, $user);
597 return $count;