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