Merge branch '44255-27' of git://github.com/samhemelryk/moodle
[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  * @return int id of the newly created user
33  */
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;
40     }
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');
48         }
49     }
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);
57         }
59         $userpassword = $user->password;
60         unset($user->password);
61     }
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;
68         }
69     } else {
70         $user->calendartype = $CFG->calendartype;
71     }
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);
88     }
90     // Trigger event.
91     $event = \core\event\user_created::create(
92             array(
93                 'objectid' => $newuserid,
94                 'context' => $usercontext
95                 )
96             );
97     $event->trigger();
99     return $newuserid;
102 /**
103  * Update a user with a user object (will compare against the ID)
104  *
105  * @throws moodle_exception
106  * @param stdClass $user the user to update
107  * @param bool $updatepassword if true, authentication plugin will update password.
108  */
109 function user_update_user($user, $updatepassword = true) {
110     global $DB;
112     // Set the timecreate field to the current time.
113     if (!is_object($user)) {
114         $user = (object) $user;
115     }
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');
124             }
125         }
126     }
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);
134         }
136         $passwd = $user->password;
137         unset($user->password);
138     }
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);
146         }
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);
150     }
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);
164             }
165         }
166     }
168     // Trigger event.
169     $event = \core\event\user_updated::create(
170             array(
171                 'objectid' => $user->id,
172                 'context' => context_user::instance($user->id)
173                 )
174             );
175     $event->trigger();
178 /**
179  * Marks user deleted in internal user database and notifies the auth plugin.
180  * Also unenrols user from all roles and does other cleanup.
181  *
182  * @todo Decide if this transaction is really needed (look for internal TODO:)
183  * @param object $user Userobject before delete    (without system magic quotes)
184  * @return boolean success
185  */
186 function user_delete_user($user) {
187     return delete_user($user);
190 /**
191  * Get users by id
192  *
193  * @param array $userids id of users to retrieve
194  * @return array
195  */
196 function user_get_users_by_id($userids) {
197     global $DB;
198     return $DB->get_records_list('user', 'id', $userids);
201 /**
202  * Returns the list of default 'displayable' fields
203  *
204  * Contains database field names but also names used to generate information, such as enrolledcourses
205  *
206  * @return array of user fields
207  */
208 function user_get_default_fields() {
209     return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
210         'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
211         'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
212         'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
213         'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
214         'groups', 'roles', 'preferences', 'enrolledcourses'
215     );
218 /**
219  *
220  * Give user record from mdl_user, build an array contains all user details.
221  *
222  * Warning: description file urls are 'webservice/pluginfile.php' is use.
223  *          it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
224  *
225  * @throws moodle_exception
226  * @param stdClass $user user record from mdl_user
227  * @param stdClass $course moodle course
228  * @param array $userfields required fields
229  * @return array|null
230  */
231 function user_get_user_details($user, $course = null, array $userfields = array()) {
232     global $USER, $DB, $CFG;
233     require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
234     require_once($CFG->dirroot . "/lib/filelib.php");      // File handling on description and friends.
236     $defaultfields = user_get_default_fields();
238     if (empty($userfields)) {
239         $userfields = $defaultfields;
240     }
242     foreach ($userfields as $thefield) {
243         if (!in_array($thefield, $defaultfields)) {
244             throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
245         }
246     }
248     // Make sure id and fullname are included.
249     if (!in_array('id', $userfields)) {
250         $userfields[] = 'id';
251     }
253     if (!in_array('fullname', $userfields)) {
254         $userfields[] = 'fullname';
255     }
257     if (!empty($course)) {
258         $context = context_course::instance($course->id);
259         $usercontext = context_user::instance($user->id);
260         $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
261     } else {
262         $context = context_user::instance($user->id);
263         $usercontext = $context;
264         $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
265     }
267     $currentuser = ($user->id == $USER->id);
268     $isadmin = is_siteadmin($USER);
270     $showuseridentityfields = get_extra_user_fields($context);
272     if (!empty($course)) {
273         $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
274     } else {
275         $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
276     }
277     $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
278     if (!empty($course)) {
279         $canviewuseremail = has_capability('moodle/course:useremail', $context);
280     } else {
281         $canviewuseremail = false;
282     }
283     $cannotviewdescription   = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
284     if (!empty($course)) {
285         $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
286     } else {
287         $canaccessallgroups = false;
288     }
290     if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
291         // Skip this user details.
292         return null;
293     }
295     $userdetails = array();
296     $userdetails['id'] = $user->id;
298     if (($isadmin or $currentuser) and in_array('username', $userfields)) {
299         $userdetails['username'] = $user->username;
300     }
301     if ($isadmin or $canviewfullnames) {
302         if (in_array('firstname', $userfields)) {
303             $userdetails['firstname'] = $user->firstname;
304         }
305         if (in_array('lastname', $userfields)) {
306             $userdetails['lastname'] = $user->lastname;
307         }
308     }
309     $userdetails['fullname'] = fullname($user);
311     if (in_array('customfields', $userfields)) {
312         $fields = $DB->get_recordset_sql("SELECT f.*
313                                             FROM {user_info_field} f
314                                             JOIN {user_info_category} c
315                                                  ON f.categoryid=c.id
316                                         ORDER BY c.sortorder ASC, f.sortorder ASC");
317         $userdetails['customfields'] = array();
318         foreach ($fields as $field) {
319             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
320             $newfield = 'profile_field_'.$field->datatype;
321             $formfield = new $newfield($field->id, $user->id);
322             if ($formfield->is_visible() and !$formfield->is_empty()) {
323                 $userdetails['customfields'][] =
324                     array('name' => $formfield->field->name, 'value' => $formfield->data,
325                         'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
326             }
327         }
328         $fields->close();
329         // Unset customfields if it's empty.
330         if (empty($userdetails['customfields'])) {
331             unset($userdetails['customfields']);
332         }
333     }
335     // Profile image.
336     if (in_array('profileimageurl', $userfields)) {
337         $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
338         $userdetails['profileimageurl'] = $profileimageurl->out(false);
339     }
340     if (in_array('profileimageurlsmall', $userfields)) {
341         $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
342         $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
343     }
345     // Hidden user field.
346     if ($canviewhiddenuserfields) {
347         $hiddenfields = array();
348         // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
349         // according to user/profile.php.
350         if ($user->address && in_array('address', $userfields)) {
351             $userdetails['address'] = $user->address;
352         }
353     } else {
354         $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
355     }
357     if ($user->phone1 && in_array('phone1', $userfields) &&
358             (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
359         $userdetails['phone1'] = $user->phone1;
360     }
361     if ($user->phone2 && in_array('phone2', $userfields) &&
362             (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
363         $userdetails['phone2'] = $user->phone2;
364     }
366     if (isset($user->description) &&
367         ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
368         if (in_array('description', $userfields)) {
369             // Always return the descriptionformat if description is requested.
370             list($userdetails['description'], $userdetails['descriptionformat']) =
371                     external_format_text($user->description, $user->descriptionformat,
372                             $usercontext->id, 'user', 'profile', null);
373         }
374     }
376     if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
377         $userdetails['country'] = $user->country;
378     }
380     if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
381         $userdetails['city'] = $user->city;
382     }
384     if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
385         $url = $user->url;
386         if (strpos($user->url, '://') === false) {
387             $url = 'http://'. $url;
388         }
389         $user->url = clean_param($user->url, PARAM_URL);
390         $userdetails['url'] = $user->url;
391     }
393     if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
394         $userdetails['icq'] = $user->icq;
395     }
397     if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
398         $userdetails['skype'] = $user->skype;
399     }
400     if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
401         $userdetails['yahoo'] = $user->yahoo;
402     }
403     if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
404         $userdetails['aim'] = $user->aim;
405     }
406     if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
407         $userdetails['msn'] = $user->msn;
408     }
410     if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
411         if ($user->firstaccess) {
412             $userdetails['firstaccess'] = $user->firstaccess;
413         } else {
414             $userdetails['firstaccess'] = 0;
415         }
416     }
417     if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
418         if ($user->lastaccess) {
419             $userdetails['lastaccess'] = $user->lastaccess;
420         } else {
421             $userdetails['lastaccess'] = 0;
422         }
423     }
425     if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
426       or $currentuser // Of course the current user is as well.
427       or $canviewuseremail  // This is a capability in course context, it will be false in usercontext.
428       or in_array('email', $showuseridentityfields)
429       or $user->maildisplay == 1
430       or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
431         $userdetails['email'] = $user->email;
432     }
434     if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
435         require_once($CFG->dirroot . '/tag/lib.php');
436         if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
437             $userdetails['interests'] = $interests;
438         }
439     }
441     // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
442     if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
443         if (in_array('idnumber', $userfields) && $user->idnumber) {
444             $userdetails['idnumber'] = $user->idnumber;
445         }
446     }
447     if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
448         if (in_array('institution', $userfields) && $user->institution) {
449             $userdetails['institution'] = $user->institution;
450         }
451     }
452     if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
453         if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
454             $userdetails['department'] = $user->department;
455         }
456     }
458     if (in_array('roles', $userfields) && !empty($course)) {
459         // Not a big secret.
460         $roles = get_user_roles($context, $user->id, false);
461         $userdetails['roles'] = array();
462         foreach ($roles as $role) {
463             $userdetails['roles'][] = array(
464                 'roleid'       => $role->roleid,
465                 'name'         => $role->name,
466                 'shortname'    => $role->shortname,
467                 'sortorder'    => $role->sortorder
468             );
469         }
470     }
472     // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
473     if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
474         $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
475                 'g.id, g.name,g.description,g.descriptionformat');
476         $userdetails['groups'] = array();
477         foreach ($usergroups as $group) {
478             list($group->description, $group->descriptionformat) =
479                 external_format_text($group->description, $group->descriptionformat,
480                         $context->id, 'group', 'description', $group->id);
481             $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
482                 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
483         }
484     }
485     // List of courses where the user is enrolled.
486     if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
487         $enrolledcourses = array();
488         if ($mycourses = enrol_get_users_courses($user->id, true)) {
489             foreach ($mycourses as $mycourse) {
490                 if ($mycourse->category) {
491                     $coursecontext = context_course::instance($mycourse->id);
492                     $enrolledcourse = array();
493                     $enrolledcourse['id'] = $mycourse->id;
494                     $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
495                     $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
496                     $enrolledcourses[] = $enrolledcourse;
497                 }
498             }
499             $userdetails['enrolledcourses'] = $enrolledcourses;
500         }
501     }
503     // User preferences.
504     if (in_array('preferences', $userfields) && $currentuser) {
505         $preferences = array();
506         $userpreferences = get_user_preferences();
507         foreach ($userpreferences as $prefname => $prefvalue) {
508             $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
509         }
510         $userdetails['preferences'] = $preferences;
511     }
513     return $userdetails;
516 /**
517  * Tries to obtain user details, either recurring directly to the user's system profile
518  * or through one of the user's course enrollments (course profile).
519  *
520  * @param stdClass $user The user.
521  * @return array if unsuccessful or the allowed user details.
522  */
523 function user_get_user_details_courses($user) {
524     global $USER;
525     $userdetails = null;
527     // Get the courses that the user is enrolled in (only active).
528     $courses = enrol_get_users_courses($user->id, true);
530     $systemprofile = false;
531     if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
532         $systemprofile = true;
533     }
535     // Try using system profile.
536     if ($systemprofile) {
537         $userdetails = user_get_user_details($user, null);
538     } else {
539         // Try through course profile.
540         foreach ($courses as $course) {
541             if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
542                 $userdetails = user_get_user_details($user, $course);
543             }
544         }
545     }
547     return $userdetails;
550 /**
551  * Check if $USER have the necessary capabilities to obtain user details.
552  *
553  * @param stdClass $user
554  * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
555  * @return bool true if $USER can view user details.
556  */
557 function can_view_user_details_cap($user, $course = null) {
558     // Check $USER has the capability to view the user details at user context.
559     $usercontext = context_user::instance($user->id);
560     $result = has_capability('moodle/user:viewdetails', $usercontext);
561     // Otherwise can $USER see them at course context.
562     if (!$result && !empty($course)) {
563         $context = context_course::instance($course->id);
564         $result = has_capability('moodle/user:viewdetails', $context);
565     }
566     return $result;
569 /**
570  * Return a list of page types
571  * @param string $pagetype current page type
572  * @param stdClass $parentcontext Block's parent context
573  * @param stdClass $currentcontext Current context of block
574  * @return array
575  */
576 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
577     return array('user-profile' => get_string('page-user-profile', 'pagetype'));