3e3b75456e2fe74c3f9eda51c667a273fc7e628c
[moodle.git] / user / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * External user API
20  *
21  * @package    moodlecore
22  * @subpackage user
23  * @copyright  2009 Moodle Pty Ltd (http://moodle.com)
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
28 /**
29  * Creates a user
30  *
31  * @param stdClass $user user to create
32  * @param bool $updatepassword if true, authentication plugin will update password.
33  * @return int id of the newly created user
34  */
35 function user_create_user($user, $updatepassword = true) {
36     global $CFG, $DB;
38     // Set the timecreate field to the current time.
39     if (!is_object($user)) {
40         $user = (object) $user;
41     }
43     // Check username.
44     if ($user->username !== core_text::strtolower($user->username)) {
45         throw new moodle_exception('usernamelowercase');
46     } else {
47         if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
48             throw new moodle_exception('invalidusername');
49         }
50     }
52     // Save the password in a temp value for later.
53     if ($updatepassword && isset($user->password)) {
55         // Check password toward the password policy.
56         if (!check_password_policy($user->password, $errmsg)) {
57             throw new moodle_exception($errmsg);
58         }
60         $userpassword = $user->password;
61         unset($user->password);
62     }
64     // Make sure calendartype, if set, is valid.
65     if (!empty($user->calendartype)) {
66         $availablecalendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
67         if (empty($availablecalendartypes[$user->calendartype])) {
68             $user->calendartype = $CFG->calendartype;
69         }
70     } else {
71         $user->calendartype = $CFG->calendartype;
72     }
74     $user->timecreated = time();
75     $user->timemodified = $user->timecreated;
77     // Insert the user into the database.
78     $newuserid = $DB->insert_record('user', $user);
80     // Create USER context for this user.
81     $usercontext = context_user::instance($newuserid);
83     // Update user password if necessary.
84     if (isset($userpassword)) {
85         // Get full database user row, in case auth is default.
86         $newuser = $DB->get_record('user', array('id' => $newuserid));
87         $authplugin = get_auth_plugin($newuser->auth);
88         $authplugin->user_update_password($newuser, $userpassword);
89     }
91     // Trigger event.
92     $event = \core\event\user_created::create(
93             array(
94                 'objectid' => $newuserid,
95                 'context' => $usercontext
96                 )
97             );
98     $event->trigger();
100     return $newuserid;
103 /**
104  * Update a user with a user object (will compare against the ID)
105  *
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  * @param array $userids id of users to retrieve
193  *
194  */
195 function user_get_users_by_id($userids) {
196     global $DB;
197     return $DB->get_records_list('user', 'id', $userids);
200 /**
201  * Returns the list of default 'displayable' fields
202  *
203  * Contains database field names but also names used to generate information, such as enrolledcourses
204  *
205  * @return array of user fields
206  */
207 function user_get_default_fields() {
208     return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
209         'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
210         'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
211         'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
212         'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
213         'groups', 'roles', 'preferences', 'enrolledcourses'
214     );
217 /**
218  *
219  * Give user record from mdl_user, build an array conntains
220  * 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  * @param stdClass $user user record from mdl_user
226  * @param stdClass $context context object
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     }
249     // Make sure id and fullname are included
250     if (!in_array('id', $userfields)) {
251         $userfields[] = 'id';
252     }
254     if (!in_array('fullname', $userfields)) {
255         $userfields[] = 'fullname';
256     }
258     if (!empty($course)) {
259         $context = context_course::instance($course->id);
260         $usercontext = context_user::instance($user->id);
261         $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
262     } else {
263         $context = context_user::instance($user->id);
264         $usercontext = $context;
265         $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
266     }
268     $currentuser = ($user->id == $USER->id);
269     $isadmin = is_siteadmin($USER);
271     $showuseridentityfields = get_extra_user_fields($context);
273     if (!empty($course)) {
274         $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
275     } else {
276         $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
277     }
278     $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
279     if (!empty($course)) {
280         $canviewuseremail = has_capability('moodle/course:useremail', $context);
281     } else {
282         $canviewuseremail = false;
283     }
284     $cannotviewdescription   = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid'=>$user->id));
285     if (!empty($course)) {
286         $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
287     } else {
288         $canaccessallgroups = false;
289     }
291     if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
292         // skip this user details
293         return null;
294     }
296     $userdetails = array();
297     $userdetails['id'] = $user->id;
299     if (($isadmin or $currentuser) and in_array('username', $userfields)) {
300         $userdetails['username'] = $user->username;
301     }
302     if ($isadmin or $canviewfullnames) {
303         if (in_array('firstname', $userfields)) {
304             $userdetails['firstname'] = $user->firstname;
305         }
306         if (in_array('lastname', $userfields)) {
307             $userdetails['lastname'] = $user->lastname;
308         }
309     }
310     $userdetails['fullname'] = fullname($user);
312     if (in_array('customfields', $userfields)) {
313         $fields = $DB->get_recordset_sql("SELECT f.*
314                                             FROM {user_info_field} f
315                                             JOIN {user_info_category} c
316                                                  ON f.categoryid=c.id
317                                         ORDER BY c.sortorder ASC, f.sortorder ASC");
318         $userdetails['customfields'] = array();
319         foreach ($fields as $field) {
320             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
321             $newfield = 'profile_field_'.$field->datatype;
322             $formfield = new $newfield($field->id, $user->id);
323             if ($formfield->is_visible() and !$formfield->is_empty()) {
324                 $userdetails['customfields'][] =
325                     array('name' => $formfield->field->name, 'value' => $formfield->data,
326                         'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
327             }
328         }
329         $fields->close();
330         // unset customfields if it's empty
331         if (empty($userdetails['customfields'])) {
332             unset($userdetails['customfields']);
333         }
334     }
336     // profile image
337     if (in_array('profileimageurl', $userfields)) {
338         $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f1');
339         $userdetails['profileimageurl'] = $profileimageurl->out(false);
340     }
341     if (in_array('profileimageurlsmall', $userfields)) {
342         $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', 'f2');
343         $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
344     }
346     //hidden user field
347     if ($canviewhiddenuserfields) {
348         $hiddenfields = array();
349         // address, phone1 and phone2 not appears in hidden fields list
350         // but require viewhiddenfields capability
351         // according to user/profile.php
352         if ($user->address && in_array('address', $userfields)) {
353             $userdetails['address'] = $user->address;
354         }
355     } else {
356         $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
357     }
359     if ($user->phone1 && in_array('phone1', $userfields) &&
360             (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
361         $userdetails['phone1'] = $user->phone1;
362     }
363     if ($user->phone2 && in_array('phone2', $userfields) &&
364             (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
365         $userdetails['phone2'] = $user->phone2;
366     }
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);
375         }
376     }
378     if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
379         $userdetails['country'] = $user->country;
380     }
382     if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
383         $userdetails['city'] = $user->city;
384     }
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;
390         }
391         $user->url = clean_param($user->url, PARAM_URL);
392         $userdetails['url'] = $user->url;
393     }
395     if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
396         $userdetails['icq'] = $user->icq;
397     }
399     if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
400         $userdetails['skype'] = $user->skype;
401     }
402     if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
403         $userdetails['yahoo'] = $user->yahoo;
404     }
405     if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
406         $userdetails['aim'] = $user->aim;
407     }
408     if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
409         $userdetails['msn'] = $user->msn;
410     }
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;
417         }
418     }
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;
424         }
425     }
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;
434     }
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;
440         }
441     }
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;
447         }
448     }
449     if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
450         if (in_array('institution', $userfields) && $user->institution) {
451             $userdetails['institution'] = $user->institution;
452         }
453     }
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;
457         }
458     }
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
470             );
471         }
472     }
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);
485         }
486     }
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;
499                 }
500             }
501             $userdetails['enrolledcourses'] = $enrolledcourses;
502         }
503     }
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);
511          }
512          $userdetails['preferences'] = $preferences;
513     }
515     return $userdetails;
518 /**
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).
521  *
522  * @param object $user The user.
523  * @return array if unsuccessful or the allowed user details.
524  */
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;
535     }
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);
545             }
546         }
547     }
549     return $userdetails;
552 /**
553  * Check if $USER have the necessary capabilities to obtain user details.
554  *
555  * @param object $user
556  * @param object $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.
558  */
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);
567     }
568     return $result;
571 /**
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  */
577 function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
578     return array('user-profile'=>get_string('page-user-profile', 'pagetype'));