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