MDL-50130 users: Filter profile fields data in external functions
[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
JL
338
339 // We only use display_data in fields that require text formatting.
340 if ($field->datatype == 'text' or $field->datatype == 'textarea') {
341 $fieldvalue = $formfield->display_data();
342 } else {
343 // Cases: datetime, checkbox and menu.
344 $fieldvalue = $formfield->data;
345 }
346
ad7612f5 347 $userdetails['customfields'][] =
f804c730 348 array('name' => $formfield->field->name, 'value' => $fieldvalue,
ad7612f5
DC
349 'type' => $field->datatype, 'shortname' => $formfield->field->shortname);
350 }
351 }
352 $fields->close();
a2ed6e69 353 // Unset customfields if it's empty.
ad7612f5
DC
354 if (empty($userdetails['customfields'])) {
355 unset($userdetails['customfields']);
01479290 356 }
01479290
DC
357 }
358
a2ed6e69 359 // Profile image.
ad7612f5 360 if (in_array('profileimageurl', $userfields)) {
a2ed6e69 361 $profileimageurl = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f1');
ad7612f5
DC
362 $userdetails['profileimageurl'] = $profileimageurl->out(false);
363 }
364 if (in_array('profileimageurlsmall', $userfields)) {
a2ed6e69 365 $profileimageurlsmall = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', null, '/', 'f2');
ad7612f5
DC
366 $userdetails['profileimageurlsmall'] = $profileimageurlsmall->out(false);
367 }
01479290 368
a2ed6e69 369 // Hidden user field.
01479290
DC
370 if ($canviewhiddenuserfields) {
371 $hiddenfields = array();
a2ed6e69
SH
372 // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
373 // according to user/profile.php.
ad7612f5 374 if ($user->address && in_array('address', $userfields)) {
01479290
DC
375 $userdetails['address'] = $user->address;
376 }
01479290
DC
377 } else {
378 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
379 }
380
48a7b182 381 if ($user->phone1 && in_array('phone1', $userfields) &&
58f739c5 382 (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
383 $userdetails['phone1'] = $user->phone1;
384 }
385 if ($user->phone2 && in_array('phone2', $userfields) &&
58f739c5 386 (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
387 $userdetails['phone2'] = $user->phone2;
388 }
389
acf64596
JM
390 if (isset($user->description) &&
391 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
392 if (in_array('description', $userfields)) {
393 // Always return the descriptionformat if description is requested.
394 list($userdetails['description'], $userdetails['descriptionformat']) =
395 external_format_text($user->description, $user->descriptionformat,
396 $usercontext->id, 'user', 'profile', null);
01479290
DC
397 }
398 }
399
ad7612f5 400 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
01479290
DC
401 $userdetails['country'] = $user->country;
402 }
403
ad7612f5 404 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
01479290
DC
405 $userdetails['city'] = $user->city;
406 }
407
ad7612f5 408 if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
01479290
DC
409 $url = $user->url;
410 if (strpos($user->url, '://') === false) {
411 $url = 'http://'. $url;
412 }
413 $user->url = clean_param($user->url, PARAM_URL);
414 $userdetails['url'] = $user->url;
415 }
416
ad7612f5 417 if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
01479290
DC
418 $userdetails['icq'] = $user->icq;
419 }
420
ad7612f5 421 if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
01479290
DC
422 $userdetails['skype'] = $user->skype;
423 }
ad7612f5 424 if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
01479290
DC
425 $userdetails['yahoo'] = $user->yahoo;
426 }
ad7612f5 427 if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
01479290
DC
428 $userdetails['aim'] = $user->aim;
429 }
ad7612f5 430 if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
01479290
DC
431 $userdetails['msn'] = $user->msn;
432 }
433
ad7612f5 434 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
01479290
DC
435 if ($user->firstaccess) {
436 $userdetails['firstaccess'] = $user->firstaccess;
437 } else {
438 $userdetails['firstaccess'] = 0;
439 }
440 }
ad7612f5 441 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
01479290
DC
442 if ($user->lastaccess) {
443 $userdetails['lastaccess'] = $user->lastaccess;
444 } else {
445 $userdetails['lastaccess'] = 0;
446 }
447 }
448
a2ed6e69
SH
449 if (in_array('email', $userfields) && ($isadmin // The admin is allowed the users email.
450 or $currentuser // Of course the current user is as well.
451 or $canviewuseremail // This is a capability in course context, it will be false in usercontext.
58f739c5 452 or in_array('email', $showuseridentityfields)
01479290 453 or $user->maildisplay == 1
ca774c94 454 or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
90e30b96 455 $userdetails['email'] = $user->email;
01479290
DC
456 }
457
ad7612f5 458 if (in_array('interests', $userfields) && !empty($CFG->usetags)) {
01479290
DC
459 require_once($CFG->dirroot . '/tag/lib.php');
460 if ($interests = tag_get_tags_csv('user', $user->id, TAG_RETURN_TEXT) ) {
461 $userdetails['interests'] = $interests;
462 }
463 }
464
a2ed6e69 465 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
58f739c5 466 if ($isadmin or $currentuser or in_array('idnumber', $showuseridentityfields)) {
48a7b182 467 if (in_array('idnumber', $userfields) && $user->idnumber) {
3a3f3b22
CW
468 $userdetails['idnumber'] = $user->idnumber;
469 }
48a7b182 470 }
58f739c5 471 if ($isadmin or $currentuser or in_array('institution', $showuseridentityfields)) {
ad7612f5 472 if (in_array('institution', $userfields) && $user->institution) {
01479290
DC
473 $userdetails['institution'] = $user->institution;
474 }
48a7b182 475 }
58f739c5 476 if ($isadmin or $currentuser or in_array('department', $showuseridentityfields)) {
a2ed6e69 477 if (in_array('department', $userfields) && isset($user->department)) { // Isset because it's ok to have department 0.
01479290
DC
478 $userdetails['department'] = $user->department;
479 }
480 }
481
ad7612f5 482 if (in_array('roles', $userfields) && !empty($course)) {
a2ed6e69 483 // Not a big secret.
01479290
DC
484 $roles = get_user_roles($context, $user->id, false);
485 $userdetails['roles'] = array();
486 foreach ($roles as $role) {
487 $userdetails['roles'][] = array(
488 'roleid' => $role->roleid,
489 'name' => $role->name,
490 'shortname' => $role->shortname,
491 'sortorder' => $role->sortorder
492 );
493 }
494 }
495
a2ed6e69 496 // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
ad7612f5 497 if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
93ce0e82
JM
498 $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
499 'g.id, g.name,g.description,g.descriptionformat');
01479290
DC
500 $userdetails['groups'] = array();
501 foreach ($usergroups as $group) {
93ce0e82
JM
502 list($group->description, $group->descriptionformat) =
503 external_format_text($group->description, $group->descriptionformat,
504 $context->id, 'group', 'description', $group->id);
a2ed6e69
SH
505 $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
506 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
01479290
DC
507 }
508 }
a2ed6e69 509 // List of courses where the user is enrolled.
ad7612f5 510 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
01479290
DC
511 $enrolledcourses = array();
512 if ($mycourses = enrol_get_users_courses($user->id, true)) {
513 foreach ($mycourses as $mycourse) {
514 if ($mycourse->category) {
43731030 515 $coursecontext = context_course::instance($mycourse->id);
01479290
DC
516 $enrolledcourse = array();
517 $enrolledcourse['id'] = $mycourse->id;
43ff71a0 518 $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
8ebbb06a 519 $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
01479290
DC
520 $enrolledcourses[] = $enrolledcourse;
521 }
522 }
523 $userdetails['enrolledcourses'] = $enrolledcourses;
524 }
525 }
526
a2ed6e69 527 // User preferences.
ad7612f5 528 if (in_array('preferences', $userfields) && $currentuser) {
01479290
DC
529 $preferences = array();
530 $userpreferences = get_user_preferences();
a2ed6e69 531 foreach ($userpreferences as $prefname => $prefvalue) {
01479290 532 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
a2ed6e69
SH
533 }
534 $userdetails['preferences'] = $preferences;
01479290 535 }
ad7612f5 536
01479290
DC
537 return $userdetails;
538}
539
86477112
FS
540/**
541 * Tries to obtain user details, either recurring directly to the user's system profile
c70b9853 542 * or through one of the user's course enrollments (course profile).
86477112 543 *
a2ed6e69 544 * @param stdClass $user The user.
c70b9853 545 * @return array if unsuccessful or the allowed user details.
86477112
FS
546 */
547function user_get_user_details_courses($user) {
548 global $USER;
549 $userdetails = null;
550
a2ed6e69 551 // Get the courses that the user is enrolled in (only active).
86477112
FS
552 $courses = enrol_get_users_courses($user->id, true);
553
c70b9853
JM
554 $systemprofile = false;
555 if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
556 $systemprofile = true;
557 }
86477112 558
c70b9853 559 // Try using system profile.
86477112
FS
560 if ($systemprofile) {
561 $userdetails = user_get_user_details($user, null);
562 } else {
c70b9853 563 // Try through course profile.
86477112 564 foreach ($courses as $course) {
854859e1 565 if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
86477112
FS
566 $userdetails = user_get_user_details($user, $course);
567 }
568 }
569 }
570
571 return $userdetails;
572}
573
574/**
c70b9853
JM
575 * Check if $USER have the necessary capabilities to obtain user details.
576 *
a2ed6e69
SH
577 * @param stdClass $user
578 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
c70b9853 579 * @return bool true if $USER can view user details.
86477112
FS
580 */
581function can_view_user_details_cap($user, $course = null) {
c70b9853 582 // Check $USER has the capability to view the user details at user context.
bea86e84 583 $usercontext = context_user::instance($user->id);
c70b9853
JM
584 $result = has_capability('moodle/user:viewdetails', $usercontext);
585 // Otherwise can $USER see them at course context.
586 if (!$result && !empty($course)) {
bea86e84 587 $context = context_course::instance($course->id);
c70b9853 588 $result = has_capability('moodle/user:viewdetails', $context);
86477112
FS
589 }
590 return $result;
591}
592
b1627a92
DC
593/**
594 * Return a list of page types
595 * @param string $pagetype current page type
596 * @param stdClass $parentcontext Block's parent context
597 * @param stdClass $currentcontext Current context of block
a2ed6e69 598 * @return array
b1627a92 599 */
b38e2e28 600function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
a2ed6e69 601 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
d6731600 602}
52dc1de7
AA
603
604/**
605 * Count the number of failed login attempts for the given user, since last successful login.
606 *
607 * @param int|stdclass $user user id or object.
608 * @param bool $reset Resets failed login count, if set to true.
609 *
610 * @return int number of failed login attempts since the last successful login.
611 */
612function user_count_login_failures($user, $reset = true) {
613 global $DB;
614
615 if (!is_object($user)) {
616 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
617 }
618 if ($user->deleted) {
619 // Deleted user, nothing to do.
620 return 0;
621 }
622 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
623 if ($reset) {
624 set_user_preference('login_failed_count_since_success', 0, $user);
625 }
626 return $count;
627}
628
b865a3e2 629/**
22893d6f
JC
630 * Converts a string into a flat array of menu items, where each menu items is a
631 * stdClass with fields type, url, title, pix, and imgsrc.
b865a3e2
JC
632 *
633 * @param string $text the menu items definition
634 * @param moodle_page $page the current page
635 * @return array
636 */
637function user_convert_text_to_menu_items($text, $page) {
638 global $OUTPUT, $CFG;
639
640 $lines = explode("\n", $text);
641 $items = array();
642 $lastchild = null;
643 $lastdepth = null;
644 $lastsort = 0;
088fcf90 645 $children = array();
b865a3e2
JC
646 foreach ($lines as $line) {
647 $line = trim($line);
648 $bits = explode('|', $line, 3);
22893d6f
JC
649 $itemtype = 'link';
650 if (preg_match("/^#+$/", $line)) {
651 $itemtype = 'divider';
652 } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
b865a3e2
JC
653 // Every item must have a name to be valid.
654 continue;
655 } else {
656 $bits[0] = ltrim($bits[0], '-');
657 }
658
659 // Create the child.
660 $child = new stdClass();
22893d6f
JC
661 $child->itemtype = $itemtype;
662 if ($itemtype === 'divider') {
663 // Add the divider to the list of children and skip link
664 // processing.
665 $children[] = $child;
666 continue;
667 }
b865a3e2
JC
668
669 // Name processing.
670 $namebits = explode(',', $bits[0], 2);
671 if (count($namebits) == 2) {
06c27531
AN
672 // Check the validity of the identifier part of the string.
673 if (clean_param($namebits[0], PARAM_STRINGID) !== '') {
674 // Treat this as a language string.
675 $child->title = get_string($namebits[0], $namebits[1]);
676 }
677 }
678 if (empty($child->title)) {
b865a3e2
JC
679 // Use it as is, don't even clean it.
680 $child->title = $bits[0];
681 }
682
683 // URL processing.
684 if (!array_key_exists(1, $bits) or empty($bits[1])) {
22893d6f 685 // Set the url to null, and set the itemtype to invalid.
b865a3e2 686 $bits[1] = null;
22893d6f 687 $child->itemtype = "invalid";
b865a3e2 688 } else {
4887d152 689 // Nasty hack to replace the grades with the direct url.
c9451960
AG
690 if (strpos($bits[1], '/grade/report/mygrades.php') !== false) {
691 $bits[1] = user_mygrades_url();
692 }
693
b865a3e2
JC
694 // Make sure the url is a moodle url.
695 $bits[1] = new moodle_url(trim($bits[1]));
696 }
697 $child->url = $bits[1];
698
699 // PIX processing.
700 $pixpath = "t/edit";
701 if (!array_key_exists(2, $bits) or empty($bits[2])) {
702 // Use the default.
703 $child->pix = $pixpath;
704 } else {
705 // Check for the specified image existing.
706 $pixpath = "t/" . $bits[2];
707 if ($page->theme->resolve_image_location($pixpath, 'moodle', true)) {
708 // Use the image.
709 $child->pix = $pixpath;
710 } else {
711 // Treat it like a URL.
712 $child->pix = null;
713 $child->imgsrc = $bits[2];
714 }
715 }
716
717 // Add this child to the list of children.
718 $children[] = $child;
719 }
720 return $children;
721}
722
6da0e4cf
JC
723/**
724 * Get a list of essential user navigation items.
725 *
726 * @param stdclass $user user object.
727 * @param moodle_page $page page object.
728 * @return stdClass $returnobj navigation information object, where:
729 *
730 * $returnobj->navitems array array of links where each link is a
731 * stdClass with fields url, title, and
732 * pix
733 * $returnobj->metadata array array of useful user metadata to be
734 * used when constructing navigation;
735 * fields include:
736 *
737 * ROLE FIELDS
738 * asotherrole bool whether viewing as another role
739 * rolename string name of the role
740 *
741 * USER FIELDS
742 * These fields are for the currently-logged in user, or for
743 * the user that the real user is currently logged in as.
744 *
745 * userid int the id of the user in question
746 * userfullname string the user's full name
747 * userprofileurl moodle_url the url of the user's profile
748 * useravatar string a HTML fragment - the rendered
749 * user_picture for this user
750 * userloginfail string an error string denoting the number
751 * of login failures since last login
752 *
753 * "REAL USER" FIELDS
754 * These fields are for when asotheruser is true, and
755 * correspond to the underlying "real user".
756 *
757 * asotheruser bool whether viewing as another user
758 * realuserid int the id of the user in question
759 * realuserfullname string the user's full name
760 * realuserprofileurl moodle_url the url of the user's profile
761 * realuseravatar string a HTML fragment - the rendered
762 * user_picture for this user
763 *
764 * MNET PROVIDER FIELDS
765 * asmnetuser bool whether viewing as a user from an
766 * MNet provider
767 * mnetidprovidername string name of the MNet provider
768 * mnetidproviderwwwroot string URL of the MNet provider
769 */
770function user_get_user_navigation_info($user, $page) {
771 global $OUTPUT, $DB, $SESSION, $CFG;
772
773 $returnobject = new stdClass();
774 $returnobject->navitems = array();
775 $returnobject->metadata = array();
776
777 $course = $page->course;
778
779 // Query the environment.
780 $context = context_course::instance($course->id);
781
782 // Get basic user metadata.
783 $returnobject->metadata['userid'] = $user->id;
784 $returnobject->metadata['userfullname'] = fullname($user, true);
785 $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
786 'id' => $user->id
787 ));
788 $returnobject->metadata['useravatar'] = $OUTPUT->user_picture (
789 $user,
790 array(
791 'link' => false,
792 'visibletoscreenreaders' => false
793 )
794 );
795 // Build a list of items for a regular user.
796
797 // Query MNet status.
798 if ($returnobject->metadata['asmnetuser'] = is_mnet_remote_user($user)) {
799 $mnetidprovider = $DB->get_record('mnet_host', array('id' => $user->mnethostid));
800 $returnobject->metadata['mnetidprovidername'] = $mnetidprovider->name;
801 $returnobject->metadata['mnetidproviderwwwroot'] = $mnetidprovider->wwwroot;
802 }
803
804 // Did the user just log in?
805 if (isset($SESSION->justloggedin)) {
806 // Don't unset this flag as login_info still needs it.
807 if (!empty($CFG->displayloginfailures)) {
808 // We're already in /user/lib.php, so we don't need to include.
809 if ($count = user_count_login_failures($user)) {
810
811 // Get login failures string.
812 $a = new stdClass();
813 $a->attempts = html_writer::tag('span', $count, array('class' => 'value'));
814 $returnobject->metadata['userloginfail'] =
815 get_string('failedloginattempts', '', $a);
816
817 }
818 }
819 }
820
81d7de1a 821 // Links: Dashboard.
6da0e4cf 822 $myhome = new stdClass();
22893d6f 823 $myhome->itemtype = 'link';
6da0e4cf
JC
824 $myhome->url = new moodle_url('/my/');
825 $myhome->title = get_string('mymoodle', 'admin');
826 $myhome->pix = "i/course";
827 $returnobject->navitems[] = $myhome;
828
829 // Links: My Profile.
830 $myprofile = new stdClass();
22893d6f 831 $myprofile->itemtype = 'link';
6da0e4cf 832 $myprofile->url = new moodle_url('/user/profile.php', array('id' => $user->id));
4887d152 833 $myprofile->title = get_string('profile');
6da0e4cf
JC
834 $myprofile->pix = "i/user";
835 $returnobject->navitems[] = $myprofile;
836
837 // Links: Role-return or logout link.
838 $lastobj = null;
839 $buildlogout = true;
840 $returnobject->metadata['asotherrole'] = false;
841 if (is_role_switched($course->id)) {
842 if ($role = $DB->get_record('role', array('id' => $user->access['rsw'][$context->path]))) {
843 // Build role-return link instead of logout link.
844 $rolereturn = new stdClass();
22893d6f 845 $rolereturn->itemtype = 'link';
6da0e4cf
JC
846 $rolereturn->url = new moodle_url('/course/switchrole.php', array(
847 'id' => $course->id,
848 'sesskey' => sesskey(),
849 'switchrole' => 0,
850 'returnurl' => $page->url->out_as_local_url(false)
851 ));
852 $rolereturn->pix = "a/logout";
853 $rolereturn->title = get_string('switchrolereturn');
854 $lastobj = $rolereturn;
855
856 $returnobject->metadata['asotherrole'] = true;
857 $returnobject->metadata['rolename'] = role_get_name($role, $context);
858
859 $buildlogout = false;
860 }
861 }
862
863 if ($returnobject->metadata['asotheruser'] = \core\session\manager::is_loggedinas()) {
864 $realuser = \core\session\manager::get_realuser();
865
866 // Save values for the real user, as $user will be full of data for the
867 // user the user is disguised as.
868 $returnobject->metadata['realuserid'] = $realuser->id;
869 $returnobject->metadata['realuserfullname'] = fullname($realuser, true);
870 $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
871 'id' => $realuser->id
872 ));
873 $returnobject->metadata['realuseravatar'] = $OUTPUT->user_picture (
874 $realuser,
875 array(
876 'link' => false,
877 'visibletoscreenreaders' => false
878 )
879 );
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}