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