MDL-50839 user: realuseravatar should also read avatarsize
[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()) {
f804c730 338
e96e66aa
EL
339 // TODO: Part of MDL-50728, this conditional coding must be moved to
340 // proper profile fields API so they are self-contained.
f804c730
JL
341 // We only use display_data in fields that require text formatting.
342 if ($field->datatype == 'text' or $field->datatype == 'textarea') {
343 $fieldvalue = $formfield->display_data();
344 } else {
345 // Cases: datetime, checkbox and menu.
346 $fieldvalue = $formfield->data;
347 }
348
ad7612f5 349 $userdetails['customfields'][] =
f804c730 350 array('name' => $formfield->field->name, 'value' => $fieldvalue,
ad7612f5
DC
351 'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
352 }
353 }
354 $fields->close();
a2ed6e69 355 // Unset customfields if it's empty.
ad7612f5
DC
356 if (empty($userdetails['customfields'])) {
357 unset($userdetails['customfields']);
01479290 358 }
01479290
DC
359 }
360
a2ed6e69 361 // Profile image.
ad7612f5 362 if (in_array('profileimageurl', $userfields)) {
a2ed6e69 363 $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
ad7612f5
DC
364 $userdetails['profileimageurl'] = $profileimageurl->out(false);
365 }
366 if (in_array('profileimageurlsmall', $userfields)) {
a2ed6e69 367 $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
ad7612f5
DC
368 $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
369 }
01479290 370
a2ed6e69 371 // Hidden user field.
01479290
DC
372 if ($canviewhiddenuserfields) {
373 $hiddenfields = array();
a2ed6e69
SH
374 // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
375 // according to user/profile.php.
ad7612f5 376 if ($user->address && in_array('address', $userfields)) {
01479290
DC
377 $userdetails['address'] = $user->address;
378 }
01479290
DC
379 } else {
380 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
381 }
382
48a7b182 383 if ($user->phone1 && in_array('phone1', $userfields) &&
58f739c5 384 (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
385 $userdetails['phone1'] = $user->phone1;
386 }
387 if ($user->phone2 && in_array('phone2', $userfields) &&
58f739c5 388 (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
389 $userdetails['phone2'] = $user->phone2;
390 }
391
acf64596
JM
392 if (isset($user->description) &&
393 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
394 if (in_array('description', $userfields)) {
395 // Always return the descriptionformat if description is requested.
396 list($userdetails['description'], $userdetails['descriptionformat']) =
397 external_format_text($user->description, $user->descriptionformat,
398 $usercontext->id, 'user', 'profile', null);
01479290
DC
399 }
400 }
401
ad7612f5 402 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
01479290
DC
403 $userdetails['country'] = $user->country;
404 }
405
ad7612f5 406 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
01479290
DC
407 $userdetails['city'] = $user->city;
408 }
409
ad7612f5 410 if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
01479290
DC
411 $url = $user->url;
412 if (strpos($user->url, '://') === false) {
413 $url = 'http://'. $url;
414 }
415 $user->url = clean_param($user->url, PARAM_URL);
416 $userdetails['url'] = $user->url;
417 }
418
ad7612f5 419 if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
01479290
DC
420 $userdetails['icq'] = $user->icq;
421 }
422
ad7612f5 423 if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
01479290
DC
424 $userdetails['skype'] = $user->skype;
425 }
ad7612f5 426 if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
01479290
DC
427 $userdetails['yahoo'] = $user->yahoo;
428 }
ad7612f5 429 if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
01479290
DC
430 $userdetails['aim'] = $user->aim;
431 }
ad7612f5 432 if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
01479290
DC
433 $userdetails['msn'] = $user->msn;
434 }
435
ad7612f5 436 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
01479290
DC
437 if ($user->firstaccess) {
438 $userdetails['firstaccess'] = $user->firstaccess;
439 } else {
440 $userdetails['firstaccess'] = 0;
441 }
442 }
ad7612f5 443 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
01479290
DC
444 if ($user->lastaccess) {
445 $userdetails['lastaccess'] = $user->lastaccess;
446 } else {
447 $userdetails['lastaccess'] = 0;
448 }
449 }
450
a2ed6e69
SH
451 if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
452 or $currentuser // Of course the current user is as well.
453 or $canviewuseremail // This is a capability in course context, it will be false in usercontext.
58f739c5 454 or in_array('email', $showuseridentityfields)
01479290 455 or $user->maildisplay == 1
ca774c94 456 or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
90e30b96 457 $userdetails['email'] = $user->email;
01479290
DC
458 }
459
ad7612f5 460 if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
01479290
DC
461 require_once($CFG->dirroot . '/tag/lib.php');
462 if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
463 $userdetails['interests'] = $interests;
464 }
465 }
466
a2ed6e69 467 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
58f739c5 468 if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
48a7b182 469 if (in_array('idnumber', $userfields) && $user->idnumber) {
3a3f3b22
CW
470 $userdetails['idnumber'] = $user->idnumber;
471 }
48a7b182 472 }
58f739c5 473 if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
ad7612f5 474 if (in_array('institution', $userfields) && $user->institution) {
01479290
DC
475 $userdetails['institution'] = $user->institution;
476 }
48a7b182 477 }
58f739c5 478 if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
a2ed6e69 479 if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
01479290
DC
480 $userdetails['department'] = $user->department;
481 }
482 }
483
ad7612f5 484 if (in_array('roles', $userfields) && !empty($course)) {
a2ed6e69 485 // Not a big secret.
01479290
DC
486 $roles = get_user_roles($context, $user->id, false);
487 $userdetails['roles'] = array();
488 foreach ($roles as $role) {
489 $userdetails['roles'][] = array(
490 'roleid' => $role->roleid,
491 'name' => $role->name,
492 'shortname' => $role->shortname,
493 'sortorder' => $role->sortorder
494 );
495 }
496 }
497
a2ed6e69 498 // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
ad7612f5 499 if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
93ce0e82
JM
500 $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
501 'g.id, g.name,g.description,g.descriptionformat');
01479290
DC
502 $userdetails['groups'] = array();
503 foreach ($usergroups as $group) {
93ce0e82
JM
504 list($group->description, $group->descriptionformat) =
505 external_format_text($group->description, $group->descriptionformat,
506 $context->id, 'group', 'description', $group->id);
a2ed6e69
SH
507 $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
508 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
01479290
DC
509 }
510 }
a2ed6e69 511 // List of courses where the user is enrolled.
ad7612f5 512 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
01479290
DC
513 $enrolledcourses = array();
514 if ($mycourses = enrol_get_users_courses($user->id, true)) {
515 foreach ($mycourses as $mycourse) {
516 if ($mycourse->category) {
43731030 517 $coursecontext = context_course::instance($mycourse->id);
01479290
DC
518 $enrolledcourse = array();
519 $enrolledcourse['id'] = $mycourse->id;
43ff71a0 520 $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
8ebbb06a 521 $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
01479290
DC
522 $enrolledcourses[] = $enrolledcourse;
523 }
524 }
525 $userdetails['enrolledcourses'] = $enrolledcourses;
526 }
527 }
528
a2ed6e69 529 // User preferences.
ad7612f5 530 if (in_array('preferences', $userfields) && $currentuser) {
01479290
DC
531 $preferences = array();
532 $userpreferences = get_user_preferences();
a2ed6e69 533 foreach ($userpreferences as $prefname => $prefvalue) {
01479290 534 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
a2ed6e69
SH
535 }
536 $userdetails['preferences'] = $preferences;
01479290 537 }
ad7612f5 538
01479290
DC
539 return $userdetails;
540}
541
86477112
FS
542/**
543 * Tries to obtain user details, either recurring directly to the user's system profile
c70b9853 544 * or through one of the user's course enrollments (course profile).
86477112 545 *
a2ed6e69 546 * @param stdClass $user The user.
c70b9853 547 * @return array if unsuccessful or the allowed user details.
86477112
FS
548 */
549function user_get_user_details_courses($user) {
550 global $USER;
551 $userdetails = null;
552
a2ed6e69 553 // Get the courses that the user is enrolled in (only active).
86477112
FS
554 $courses = enrol_get_users_courses($user->id, true);
555
c70b9853
JM
556 $systemprofile = false;
557 if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
558 $systemprofile = true;
559 }
86477112 560
c70b9853 561 // Try using system profile.
86477112
FS
562 if ($systemprofile) {
563 $userdetails = user_get_user_details($user, null);
564 } else {
c70b9853 565 // Try through course profile.
86477112 566 foreach ($courses as $course) {
854859e1 567 if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
86477112
FS
568 $userdetails = user_get_user_details($user, $course);
569 }
570 }
571 }
572
573 return $userdetails;
574}
575
576/**
c70b9853
JM
577 * Check if $USER have the necessary capabilities to obtain user details.
578 *
a2ed6e69
SH
579 * @param stdClass $user
580 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
c70b9853 581 * @return bool true if $USER can view user details.
86477112
FS
582 */
583function can_view_user_details_cap($user, $course = null) {
c70b9853 584 // Check $USER has the capability to view the user details at user context.
bea86e84 585 $usercontext = context_user::instance($user->id);
c70b9853
JM
586 $result = has_capability('moodle/user:viewdetails', $usercontext);
587 // Otherwise can $USER see them at course context.
588 if (!$result && !empty($course)) {
bea86e84 589 $context = context_course::instance($course->id);
c70b9853 590 $result = has_capability('moodle/user:viewdetails', $context);
86477112
FS
591 }
592 return $result;
593}
594
b1627a92
DC
595/**
596 * Return a list of page types
597 * @param string $pagetype current page type
598 * @param stdClass $parentcontext Block's parent context
599 * @param stdClass $currentcontext Current context of block
a2ed6e69 600 * @return array
b1627a92 601 */
b38e2e28 602function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
a2ed6e69 603 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
d6731600 604}
52dc1de7
AA
605
606/**
607 * Count the number of failed login attempts for the given user, since last successful login.
608 *
609 * @param int|stdclass $user user id or object.
610 * @param bool $reset Resets failed login count, if set to true.
611 *
612 * @return int number of failed login attempts since the last successful login.
613 */
614function user_count_login_failures($user, $reset = true) {
615 global $DB;
616
617 if (!is_object($user)) {
618 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
619 }
620 if ($user->deleted) {
621 // Deleted user, nothing to do.
622 return 0;
623 }
624 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
625 if ($reset) {
626 set_user_preference('login_failed_count_since_success', 0, $user);
627 }
628 return $count;
629}
630
b865a3e2 631/**
22893d6f
JC
632 * Converts a string into a flat array of menu items, where each menu items is a
633 * stdClass with fields type, url, title, pix, and imgsrc.
b865a3e2
JC
634 *
635 * @param string $text the menu items definition
636 * @param moodle_page $page the current page
637 * @return array
638 */
639function user_convert_text_to_menu_items($text, $page) {
640 global $OUTPUT, $CFG;
641
642 $lines = explode("\n", $text);
643 $items = array();
644 $lastchild = null;
645 $lastdepth = null;
646 $lastsort = 0;
088fcf90 647 $children = array();
b865a3e2
JC
648 foreach ($lines as $line) {
649 $line = trim($line);
650 $bits = explode('|', $line, 3);
22893d6f
JC
651 $itemtype = 'link';
652 if (preg_match("/^#+$/", $line)) {
653 $itemtype = 'divider';
654 } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
b865a3e2
JC
655 // Every item must have a name to be valid.
656 continue;
657 } else {
658 $bits[0] = ltrim($bits[0], '-');
659 }
660
661 // Create the child.
662 $child = new stdClass();
22893d6f
JC
663 $child->itemtype = $itemtype;
664 if ($itemtype === 'divider') {
665 // Add the divider to the list of children and skip link
666 // processing.
667 $children[] = $child;
668 continue;
669 }
b865a3e2
JC
670
671 // Name processing.
672 $namebits = explode(',', $bits[0], 2);
673 if (count($namebits) == 2) {
06c27531
AN
674 // Check the validity of the identifier part of the string.
675 if (clean_param($namebits[0], PARAM_STRINGID) !== '') {
676 // Treat this as a language string.
677 $child->title = get_string($namebits[0], $namebits[1]);
678 }
679 }
680 if (empty($child->title)) {
b865a3e2
JC
681 // Use it as is, don't even clean it.
682 $child->title = $bits[0];
683 }
684
685 // URL processing.
686 if (!array_key_exists(1, $bits) or empty($bits[1])) {
22893d6f 687 // Set the url to null, and set the itemtype to invalid.
b865a3e2 688 $bits[1] = null;
22893d6f 689 $child->itemtype = "invalid";
b865a3e2 690 } else {
4887d152 691 // Nasty hack to replace the grades with the direct url.
c9451960
AG
692 if (strpos($bits[1], '/grade/report/mygrades.php') !== false) {
693 $bits[1] = user_mygrades_url();
694 }
695
b865a3e2
JC
696 // Make sure the url is a moodle url.
697 $bits[1] = new moodle_url(trim($bits[1]));
698 }
699 $child->url = $bits[1];
700
701 // PIX processing.
702 $pixpath = "t/edit";
703 if (!array_key_exists(2, $bits) or empty($bits[2])) {
704 // Use the default.
705 $child->pix = $pixpath;
706 } else {
707 // Check for the specified image existing.
708 $pixpath = "t/" . $bits[2];
709 if ($page->theme->resolve_image_location($pixpath, 'moodle', true)) {
710 // Use the image.
711 $child->pix = $pixpath;
712 } else {
713 // Treat it like a URL.
714 $child->pix = null;
715 $child->imgsrc = $bits[2];
716 }
717 }
718
719 // Add this child to the list of children.
720 $children[] = $child;
721 }
722 return $children;
723}
724
6da0e4cf
JC
725/**
726 * Get a list of essential user navigation items.
727 *
728 * @param stdclass $user user object.
729 * @param moodle_page $page page object.
9dcd5035
DB
730 * @param array $options associative array.
731 * options are:
732 * - avatarsize=35 (size of avatar image)
6da0e4cf
JC
733 * @return stdClass $returnobj navigation information object, where:
734 *
735 * $returnobj->navitems array array of links where each link is a
736 * stdClass with fields url, title, and
737 * pix
738 * $returnobj->metadata array array of useful user metadata to be
739 * used when constructing navigation;
740 * fields include:
741 *
742 * ROLE FIELDS
743 * asotherrole bool whether viewing as another role
744 * rolename string name of the role
745 *
746 * USER FIELDS
747 * These fields are for the currently-logged in user, or for
748 * the user that the real user is currently logged in as.
749 *
750 * userid int the id of the user in question
751 * userfullname string the user's full name
752 * userprofileurl moodle_url the url of the user's profile
753 * useravatar string a HTML fragment - the rendered
754 * user_picture for this user
755 * userloginfail string an error string denoting the number
756 * of login failures since last login
757 *
758 * "REAL USER" FIELDS
759 * These fields are for when asotheruser is true, and
760 * correspond to the underlying "real user".
761 *
762 * asotheruser bool whether viewing as another user
763 * realuserid int the id of the user in question
764 * realuserfullname string the user's full name
765 * realuserprofileurl moodle_url the url of the user's profile
766 * realuseravatar string a HTML fragment - the rendered
767 * user_picture for this user
768 *
769 * MNET PROVIDER FIELDS
770 * asmnetuser bool whether viewing as a user from an
771 * MNet provider
772 * mnetidprovidername string name of the MNet provider
773 * mnetidproviderwwwroot string URL of the MNet provider
774 */
c68b3206 775function user_get_user_navigation_info($user, $page, $options = array()) {
6da0e4cf
JC
776 global $OUTPUT, $DB, $SESSION, $CFG;
777
778 $returnobject = new stdClass();
779 $returnobject->navitems = array();
780 $returnobject->metadata = array();
781
782 $course = $page->course;
783
784 // Query the environment.
785 $context = context_course::instance($course->id);
786
787 // Get basic user metadata.
788 $returnobject->metadata['userid'] = $user->id;
789 $returnobject->metadata['userfullname'] = fullname($user, true);
790 $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
791 'id' => $user->id
792 ));
9dcd5035
DB
793
794 $avataroptions = array('link' => false, 'visibletoscreenreaders' => false);
795 if (!empty($options['avatarsize'])) {
796 $avataroptions['size'] = $options['avatarsize'];
797 }
6da0e4cf 798 $returnobject->metadata['useravatar'] = $OUTPUT->user_picture (
9dcd5035 799 $user, $avataroptions
6da0e4cf
JC
800 );
801 // Build a list of items for a regular user.
802
803 // Query MNet status.
804 if ($returnobject->metadata['asmnetuser'] = is_mnet_remote_user($user)) {
805 $mnetidprovider = $DB->get_record('mnet_host', array('id' => $user->mnethostid));
806 $returnobject->metadata['mnetidprovidername'] = $mnetidprovider->name;
807 $returnobject->metadata['mnetidproviderwwwroot'] = $mnetidprovider->wwwroot;
808 }
809
810 // Did the user just log in?
811 if (isset($SESSION->justloggedin)) {
812 // Don't unset this flag as login_info still needs it.
813 if (!empty($CFG->displayloginfailures)) {
814 // We're already in /user/lib.php, so we don't need to include.
815 if ($count = user_count_login_failures($user)) {
816
817 // Get login failures string.
818 $a = new stdClass();
819 $a->attempts = html_writer::tag('span', $count, array('class' => 'value'));
820 $returnobject->metadata['userloginfail'] =
821 get_string('failedloginattempts', '', $a);
822
823 }
824 }
825 }
826
81d7de1a 827 // Links: Dashboard.
6da0e4cf 828 $myhome = new stdClass();
22893d6f 829 $myhome->itemtype = 'link';
6da0e4cf
JC
830 $myhome->url = new moodle_url('/my/');
831 $myhome->title = get_string('mymoodle', 'admin');
832 $myhome->pix = "i/course";
833 $returnobject->navitems[] = $myhome;
834
835 // Links: My Profile.
836 $myprofile = new stdClass();
22893d6f 837 $myprofile->itemtype = 'link';
6da0e4cf 838 $myprofile->url = new moodle_url('/user/profile.php', array('id' => $user->id));
4887d152 839 $myprofile->title = get_string('profile');
6da0e4cf
JC
840 $myprofile->pix = "i/user";
841 $returnobject->navitems[] = $myprofile;
842
843 // Links: Role-return or logout link.
844 $lastobj = null;
845 $buildlogout = true;
846 $returnobject->metadata['asotherrole'] = false;
847 if (is_role_switched($course->id)) {
848 if ($role = $DB->get_record('role', array('id' => $user->access['rsw'][$context->path]))) {
849 // Build role-return link instead of logout link.
850 $rolereturn = new stdClass();
22893d6f 851 $rolereturn->itemtype = 'link';
6da0e4cf
JC
852 $rolereturn->url = new moodle_url('/course/switchrole.php', array(
853 'id' => $course->id,
854 'sesskey' => sesskey(),
855 'switchrole' => 0,
856 'returnurl' => $page->url->out_as_local_url(false)
857 ));
858 $rolereturn->pix = "a/logout";
859 $rolereturn->title = get_string('switchrolereturn');
860 $lastobj = $rolereturn;
861
862 $returnobject->metadata['asotherrole'] = true;
863 $returnobject->metadata['rolename'] = role_get_name($role, $context);
864
865 $buildlogout = false;
866 }
867 }
868
869 if ($returnobject->metadata['asotheruser'] = \core\session\manager::is_loggedinas()) {
870 $realuser = \core\session\manager::get_realuser();
871
872 // Save values for the real user, as $user will be full of data for the
873 // user the user is disguised as.
874 $returnobject->metadata['realuserid'] = $realuser->id;
875 $returnobject->metadata['realuserfullname'] = fullname($realuser, true);
876 $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
877 'id' => $realuser->id
878 ));
c68b3206 879 $returnobject->metadata['realuseravatar'] = $OUTPUT->user_picture($realuser, $avataroptions);
6da0e4cf
JC
880
881 // Build a user-revert link.
882 $userrevert = new stdClass();
22893d6f 883 $userrevert->itemtype = 'link';
6da0e4cf
JC
884 $userrevert->url = new moodle_url('/course/loginas.php', array(
885 'id' => $course->id,
886 'sesskey' => sesskey()
887 ));
888 $userrevert->pix = "a/logout";
889 $userrevert->title = get_string('logout');
890 $lastobj = $userrevert;
891
892 $buildlogout = false;
893 }
894
895 if ($buildlogout) {
896 // Build a logout link.
897 $logout = new stdClass();
22893d6f 898 $logout->itemtype = 'link';
6da0e4cf
JC
899 $logout->url = new moodle_url('/login/logout.php', array('sesskey' => sesskey()));
900 $logout->pix = "a/logout";
901 $logout->title = get_string('logout');
902 $lastobj = $logout;
903 }
904
b865a3e2
JC
905 // Before we add the last item (usually a logout link), add any
906 // custom-defined items.
907 $customitems = user_convert_text_to_menu_items($CFG->customusermenuitems, $page);
908 foreach ($customitems as $item) {
909 $returnobject->navitems[] = $item;
910 }
911
6da0e4cf
JC
912 // Add the last item to the list.
913 if (!is_null($lastobj)) {
914 $returnobject->navitems[] = $lastobj;
915 }
916
917 return $returnobject;
1d658535
PS
918}
919
920/**
921 * Add password to the list of used hashes for this user.
922 *
923 * This is supposed to be used from:
924 * 1/ change own password form
925 * 2/ password reset process
926 * 3/ user signup in auth plugins if password changing supported
927 *
928 * @param int $userid user id
929 * @param string $password plaintext password
930 * @return void
931 */
932function user_add_password_history($userid, $password) {
933 global $CFG, $DB;
934 require_once($CFG->libdir.'/password_compat/lib/password.php');
935
936 if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
937 return;
938 }
939
940 // Note: this is using separate code form normal password hashing because
941 // we need to have this under control in the future. Also the auth
942 // plugin might not store the passwords locally at all.
943
944 $record = new stdClass();
945 $record->userid = $userid;
946 $record->hash = password_hash($password, PASSWORD_DEFAULT);
947 $record->timecreated = time();
948 $DB->insert_record('user_password_history', $record);
949
950 $i = 0;
951 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
952 foreach ($records as $record) {
953 $i++;
954 if ($i > $CFG->passwordreuselimit) {
955 $DB->delete_records('user_password_history', array('id' => $record->id));
956 }
957 }
958}
959
960/**
961 * Was this password used before on change or reset password page?
962 *
963 * The $CFG->passwordreuselimit setting determines
964 * how many times different password needs to be used
965 * before allowing previously used password again.
966 *
967 * @param int $userid user id
968 * @param string $password plaintext password
969 * @return bool true if password reused
970 */
971function user_is_previously_used_password($userid, $password) {
972 global $CFG, $DB;
973 require_once($CFG->libdir.'/password_compat/lib/password.php');
974
975 if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
976 return false;
977 }
978
979 $reused = false;
980
981 $i = 0;
982 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
983 foreach ($records as $record) {
984 $i++;
985 if ($i > $CFG->passwordreuselimit) {
986 $DB->delete_records('user_password_history', array('id' => $record->id));
987 continue;
988 }
989 // NOTE: this is slow but we cannot compare the hashes directly any more.
990 if (password_verify($password, $record->hash)) {
991 $reused = true;
992 }
993 }
994
995 return $reused;
996}
3221718e
JL
997
998/**
999 * Remove a user device from the Moodle database (for PUSH notifications usually).
1000 *
1001 * @param string $uuid The device UUID.
1002 * @param string $appid The app id. If empty all the devices matching the UUID for the user will be removed.
1003 * @return bool true if removed, false if the device didn't exists in the database
1004 * @since Moodle 2.9
1005 */
1006function user_remove_user_device($uuid, $appid = "") {
1007 global $DB, $USER;
1008
1009 $conditions = array('uuid' => $uuid, 'userid' => $USER->id);
1010 if (!empty($appid)) {
1011 $conditions['appid'] = $appid;
1012 }
1013
1014 if (!$DB->count_records('user_devices', $conditions)) {
1015 return false;
1016 }
1017
1018 $DB->delete_records('user_devices', $conditions);
1019
1020 return true;
1021}
0ff203b6
JL
1022
1023/**
1024 * Trigger user_list_viewed event.
1025 *
1026 * @param stdClass $course course object
1027 * @param stdClass $context course context object
1028 * @since Moodle 2.9
1029 */
1030function user_list_view($course, $context) {
1031
1032 $event = \core\event\user_list_viewed::create(array(
1033 'objectid' => $course->id,
1034 'courseid' => $course->id,
1035 'context' => $context,
1036 'other' => array(
1037 'courseshortname' => $course->shortname,
1038 'coursefullname' => $course->fullname
1039 )
1040 ));
1041 $event->trigger();
1042}
c9451960
AG
1043
1044/**
4887d152 1045 * Returns the url to use for the "Grades" link in the user navigation.
c9451960
AG
1046 *
1047 * @param int $userid The user's ID.
1048 * @param int $courseid The course ID if available.
4887d152 1049 * @return mixed A URL to be directed to for "Grades".
c9451960
AG
1050 */
1051function user_mygrades_url($userid = null, $courseid = SITEID) {
1052 global $CFG, $USER;
1053 $url = null;
1054 if (isset($CFG->grade_mygrades_report) && $CFG->grade_mygrades_report != 'external') {
1055 if (isset($userid) && $USER->id != $userid) {
1056 // Send to the gradebook report.
1057 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report . '/index.php',
1058 array('id' => $courseid, 'userid' => $userid));
1059 } else {
1060 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report . '/index.php');
1061 }
1062 } else if (isset($CFG->grade_mygrades_report) && $CFG->grade_mygrades_report == 'external'
1063 && !empty($CFG->gradereport_mygradeurl)) {
1064 $url = $CFG->gradereport_mygradeurl;
1065 } else {
1066 $url = $CFG->wwwroot;
1067 }
1068 return $url;
1069}