MDL-62618 user: Display full names consistently on users page
[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
bb869f05
DW
25define('USER_FILTER_ENROLMENT', 1);
26define('USER_FILTER_GROUP', 2);
27define('USER_FILTER_LAST_ACCESS', 3);
28define('USER_FILTER_ROLE', 4);
29define('USER_FILTER_STATUS', 5);
30define('USER_FILTER_STRING', 6);
31
fb79269b 32/**
33 * Creates a user
adfb459c 34 *
a2ed6e69 35 * @throws moodle_exception
bb78e249
RT
36 * @param stdClass $user user to create
37 * @param bool $updatepassword if true, authentication plugin will update password.
2b55cb1b
RT
38 * @param bool $triggerevent set false if user_created event should not be triggred.
39 * This will not affect user_password_updated event triggering.
fb79269b 40 * @return int id of the newly created user
41 */
2b55cb1b 42function user_create_user($user, $updatepassword = true, $triggerevent = true) {
ac9768fc 43 global $DB;
fb79269b 44
bb78e249 45 // Set the timecreate field to the current time.
fb79269b 46 if (!is_object($user)) {
8bf0f207 47 $user = (object) $user;
fb79269b 48 }
bd0f26bd 49
bb78e249 50 // Check username.
2f1e464a 51 if ($user->username !== core_text::strtolower($user->username)) {
45b4464c
JM
52 throw new moodle_exception('usernamelowercase');
53 } else {
7a067206 54 if ($user->username !== core_user::clean_field($user->username, 'username')) {
45b4464c
JM
55 throw new moodle_exception('invalidusername');
56 }
57 }
58
bb78e249
RT
59 // Save the password in a temp value for later.
60 if ($updatepassword && isset($user->password)) {
adfb459c 61
bb78e249 62 // Check password toward the password policy.
adfb459c
JM
63 if (!check_password_policy($user->password, $errmsg)) {
64 throw new moodle_exception($errmsg);
65 }
66
67 $userpassword = $user->password;
68 unset($user->password);
69 }
bd0f26bd 70
9f7379e9 71 // Apply default values for user preferences that are stored in users table.
7a067206 72 if (!isset($user->calendartype)) {
ac9768fc 73 $user->calendartype = core_user::get_property_default('calendartype');
8bf0f207 74 }
9f7379e9 75 if (!isset($user->maildisplay)) {
ac9768fc 76 $user->maildisplay = core_user::get_property_default('maildisplay');
9f7379e9
MG
77 }
78 if (!isset($user->mailformat)) {
ac9768fc 79 $user->mailformat = core_user::get_property_default('mailformat');
9f7379e9
MG
80 }
81 if (!isset($user->maildigest)) {
ac9768fc 82 $user->maildigest = core_user::get_property_default('maildigest');
9f7379e9
MG
83 }
84 if (!isset($user->autosubscribe)) {
ac9768fc 85 $user->autosubscribe = core_user::get_property_default('autosubscribe');
9f7379e9
MG
86 }
87 if (!isset($user->trackforums)) {
ac9768fc 88 $user->trackforums = core_user::get_property_default('trackforums');
9f7379e9 89 }
7e670032 90 if (!isset($user->lang)) {
ac9768fc 91 $user->lang = core_user::get_property_default('lang');
7e670032 92 }
9f7379e9 93
fb79269b 94 $user->timecreated = time();
bd0f26bd 95 $user->timemodified = $user->timecreated;
fb79269b 96
7a067206
SL
97 // Validate user data object.
98 $uservalidation = core_user::validate($user);
99 if ($uservalidation !== true) {
100 foreach ($uservalidation as $field => $message) {
101 debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER);
102 $user->$field = core_user::clean_field($user->$field, $field);
103 }
104 }
105
bb78e249 106 // Insert the user into the database.
fb79269b 107 $newuserid = $DB->insert_record('user', $user);
108
bb78e249
RT
109 // Create USER context for this user.
110 $usercontext = context_user::instance($newuserid);
b6dcb7d9 111
bb78e249 112 // Update user password if necessary.
adfb459c 113 if (isset($userpassword)) {
bb78e249
RT
114 // Get full database user row, in case auth is default.
115 $newuser = $DB->get_record('user', array('id' => $newuserid));
adfb459c
JM
116 $authplugin = get_auth_plugin($newuser->auth);
117 $authplugin->user_update_password($newuser, $userpassword);
118 }
119
2b55cb1b
RT
120 // Trigger event If required.
121 if ($triggerevent) {
122 \core\event\user_created::create_from_userid($newuserid)->trigger();
123 }
adfb459c 124
fb79269b 125 return $newuserid;
fb79269b 126}
127
128/**
129 * Update a user with a user object (will compare against the ID)
adfb459c 130 *
a2ed6e69 131 * @throws moodle_exception
bb78e249
RT
132 * @param stdClass $user the user to update
133 * @param bool $updatepassword if true, authentication plugin will update password.
2b55cb1b
RT
134 * @param bool $triggerevent set false if user_updated event should not be triggred.
135 * This will not affect user_password_updated event triggering.
fb79269b 136 */
2b55cb1b 137function user_update_user($user, $updatepassword = true, $triggerevent = true) {
fb79269b 138 global $DB;
bd0f26bd 139
a2ed6e69 140 // Set the timecreate field to the current time.
bd0f26bd 141 if (!is_object($user)) {
8bf0f207 142 $user = (object) $user;
bd0f26bd 143 }
adfb459c 144
a2ed6e69 145 // Check username.
45b4464c 146 if (isset($user->username)) {
2f1e464a 147 if ($user->username !== core_text::strtolower($user->username)) {
45b4464c
JM
148 throw new moodle_exception('usernamelowercase');
149 } else {
7a067206 150 if ($user->username !== core_user::clean_field($user->username, 'username')) {
45b4464c
JM
151 throw new moodle_exception('invalidusername');
152 }
153 }
154 }
155
bb78e249
RT
156 // Unset password here, for updating later, if password update is required.
157 if ($updatepassword && isset($user->password)) {
adfb459c 158
a2ed6e69 159 // Check password toward the password policy.
adfb459c
JM
160 if (!check_password_policy($user->password, $errmsg)) {
161 throw new moodle_exception($errmsg);
162 }
163
9e63c0ff
FS
164 $passwd = $user->password;
165 unset($user->password);
166 }
bd0f26bd 167
8bf0f207 168 // Make sure calendartype, if set, is valid.
7a067206 169 if (empty($user->calendartype)) {
8bf0f207
MN
170 // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
171 unset($user->calendartype);
172 }
173
bd0f26bd 174 $user->timemodified = time();
7a067206
SL
175
176 // Validate user data object.
177 $uservalidation = core_user::validate($user);
178 if ($uservalidation !== true) {
179 foreach ($uservalidation as $field => $message) {
180 debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER);
181 $user->$field = core_user::clean_field($user->$field, $field);
182 }
183 }
184
fb79269b 185 $DB->update_record('user', $user);
b6dcb7d9 186
bb78e249
RT
187 if ($updatepassword) {
188 // Get full user record.
189 $updateduser = $DB->get_record('user', array('id' => $user->id));
9e63c0ff 190
a2ed6e69 191 // If password was set, then update its hash.
bb78e249
RT
192 if (isset($passwd)) {
193 $authplugin = get_auth_plugin($updateduser->auth);
194 if ($authplugin->can_change_password()) {
195 $authplugin->user_update_password($updateduser, $passwd);
196 }
adfb459c
JM
197 }
198 }
2b55cb1b
RT
199 // Trigger event if required.
200 if ($triggerevent) {
201 \core\event\user_updated::create_from_userid($user->id)->trigger();
202 }
adfb459c 203}
fb79269b 204
205/**
206 * Marks user deleted in internal user database and notifies the auth plugin.
207 * Also unenrols user from all roles and does other cleanup.
208 *
209 * @todo Decide if this transaction is really needed (look for internal TODO:)
210 * @param object $user Userobject before delete (without system magic quotes)
211 * @return boolean success
212 */
213function user_delete_user($user) {
45fb2cf8 214 return delete_user($user);
fb79269b 215}
216
217/**
218 * Get users by id
fb79269b 219 *
a2ed6e69
SH
220 * @param array $userids id of users to retrieve
221 * @return array
fb79269b 222 */
223function user_get_users_by_id($userids) {
224 global $DB;
225 return $DB->get_records_list('user', 'id', $userids);
226}
b1627a92 227
61c8e0d7
FM
228/**
229 * Returns the list of default 'displayable' fields
230 *
231 * Contains database field names but also names used to generate information, such as enrolledcourses
232 *
233 * @return array of user fields
234 */
235function user_get_default_fields() {
236 return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
237 'address', 'phone1', 'phone2', 'icq', 'skype', 'yahoo', 'aim', 'msn', 'department',
238 'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
239 'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
240 'city', 'url', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
511db621 241 'groups', 'roles', 'preferences', 'enrolledcourses', 'suspended'
61c8e0d7
FM
242 );
243}
01479290
DC
244
245/**
246 *
a2ed6e69 247 * Give user record from mdl_user, build an array contains all user details.
93ce0e82
JM
248 *
249 * Warning: description file urls are 'webservice/pluginfile.php' is use.
250 * it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
251 *
a2ed6e69 252 * @throws moodle_exception
01479290 253 * @param stdClass $user user record from mdl_user
01479290 254 * @param stdClass $course moodle course
ad7612f5 255 * @param array $userfields required fields
d6731600 256 * @return array|null
01479290 257 */
ad7612f5 258function user_get_user_details($user, $course = null, array $userfields = array()) {
1f7273af 259 global $USER, $DB, $CFG, $PAGE;
a2ed6e69
SH
260 require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
261 require_once($CFG->dirroot . "/lib/filelib.php"); // File handling on description and friends.
01479290 262
61c8e0d7 263 $defaultfields = user_get_default_fields();
ad7612f5
DC
264
265 if (empty($userfields)) {
266 $userfields = $defaultfields;
267 }
268
269 foreach ($userfields as $thefield) {
270 if (!in_array($thefield, $defaultfields)) {
271 throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
272 }
273 }
274
a2ed6e69 275 // Make sure id and fullname are included.
ad7612f5
DC
276 if (!in_array('id', $userfields)) {
277 $userfields[] = 'id';
278 }
279
280 if (!in_array('fullname', $userfields)) {
281 $userfields[] = 'fullname';
282 }
283
01479290 284 if (!empty($course)) {
43731030
FM
285 $context = context_course::instance($course->id);
286 $usercontext = context_user::instance($user->id);
1e539f64 287 $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
01479290 288 } else {
43731030 289 $context = context_user::instance($user->id);
01479290 290 $usercontext = $context;
1e539f64 291 $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
01479290
DC
292 }
293
294 $currentuser = ($user->id == $USER->id);
295 $isadmin = is_siteadmin($USER);
296
48a7b182
JM
297 $showuseridentityfields = get_extra_user_fields($context);
298
01479290
DC
299 if (!empty($course)) {
300 $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
301 } else {
302 $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
303 }
86477112 304 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
01479290
DC
305 if (!empty($course)) {
306 $canviewuseremail = has_capability('moodle/course:useremail', $context);
307 } else {
308 $canviewuseremail = false;
309 }
a2ed6e69 310 $cannotviewdescription = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
01479290
DC
311 if (!empty($course)) {
312 $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
313 } else {
314 $canaccessallgroups = false;
315 }
316
317 if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
a2ed6e69 318 // Skip this user details.
01479290
DC
319 return null;
320 }
321
322 $userdetails = array();
323 $userdetails['id'] = $user->id;
324
07d37084
DM
325 if (in_array('username', $userfields)) {
326 if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
327 $userdetails['username'] = $user->username;
328 }
01479290
DC
329 }
330 if ($isadmin or $canviewfullnames) {
ad7612f5
DC
331 if (in_array('firstname', $userfields)) {
332 $userdetails['firstname'] = $user->firstname;
333 }
334 if (in_array('lastname', $userfields)) {
335 $userdetails['lastname'] = $user->lastname;
336 }
01479290 337 }
c157e137 338 $userdetails['fullname'] = fullname($user, $canviewfullnames);
01479290 339
ad7612f5 340 if (in_array('customfields', $userfields)) {
860f59b1 341 $categories = profile_get_user_fields_with_data_by_category($user->id);
ad7612f5 342 $userdetails['customfields'] = array();
860f59b1 343 foreach ($categories as $categoryid => $fields) {
b47fda71 344 foreach ($fields as $formfield) {
860f59b1
MC
345 if ($formfield->is_visible() and !$formfield->is_empty()) {
346
347 // TODO: Part of MDL-50728, this conditional coding must be moved to
348 // proper profile fields API so they are self-contained.
349 // We only use display_data in fields that require text formatting.
b47fda71 350 if ($formfield->field->datatype == 'text' or $formfield->field->datatype == 'textarea') {
860f59b1
MC
351 $fieldvalue = $formfield->display_data();
352 } else {
353 // Cases: datetime, checkbox and menu.
354 $fieldvalue = $formfield->data;
355 }
356
357 $userdetails['customfields'][] =
358 array('name' => $formfield->field->name, 'value' => $fieldvalue,
b47fda71 359 'type' => $formfield->field->datatype, 'shortname' => $formfield->field->shortname);
f804c730 360 }
ad7612f5
DC
361 }
362 }
a2ed6e69 363 // Unset customfields if it's empty.
ad7612f5
DC
364 if (empty($userdetails['customfields'])) {
365 unset($userdetails['customfields']);
01479290 366 }
01479290
DC
367 }
368
a2ed6e69 369 // Profile image.
ad7612f5 370 if (in_array('profileimageurl', $userfields)) {
1f7273af
DP
371 $userpicture = new user_picture($user);
372 $userpicture->size = 1; // Size f1.
373 $userdetails['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
ad7612f5
DC
374 }
375 if (in_array('profileimageurlsmall', $userfields)) {
1f7273af
DP
376 if (!isset($userpicture)) {
377 $userpicture = new user_picture($user);
378 }
379 $userpicture->size = 0; // Size f2.
380 $userdetails['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
ad7612f5 381 }
01479290 382
a2ed6e69 383 // Hidden user field.
01479290
DC
384 if ($canviewhiddenuserfields) {
385 $hiddenfields = array();
a2ed6e69
SH
386 // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
387 // according to user/profile.php.
cb805753 388 if (!empty($user->address) && in_array('address', $userfields)) {
01479290
DC
389 $userdetails['address'] = $user->address;
390 }
01479290
DC
391 } else {
392 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
393 }
394
cb805753 395 if (!empty($user->phone1) && in_array('phone1', $userfields) &&
58f739c5 396 (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
397 $userdetails['phone1'] = $user->phone1;
398 }
cb805753 399 if (!empty($user->phone2) && in_array('phone2', $userfields) &&
58f739c5 400 (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
48a7b182
JM
401 $userdetails['phone2'] = $user->phone2;
402 }
403
acf64596
JM
404 if (isset($user->description) &&
405 ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
406 if (in_array('description', $userfields)) {
407 // Always return the descriptionformat if description is requested.
408 list($userdetails['description'], $userdetails['descriptionformat']) =
409 external_format_text($user->description, $user->descriptionformat,
410 $usercontext->id, 'user', 'profile', null);
01479290
DC
411 }
412 }
413
ad7612f5 414 if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
01479290
DC
415 $userdetails['country'] = $user->country;
416 }
417
ad7612f5 418 if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
01479290
DC
419 $userdetails['city'] = $user->city;
420 }
421
ad7612f5 422 if (in_array('url', $userfields) && $user->url && (!isset($hiddenfields['webpage']) or $isadmin)) {
01479290
DC
423 $url = $user->url;
424 if (strpos($user->url, '://') === false) {
425 $url = 'http://'. $url;
426 }
427 $user->url = clean_param($user->url, PARAM_URL);
428 $userdetails['url'] = $user->url;
429 }
430
ad7612f5 431 if (in_array('icq', $userfields) && $user->icq && (!isset($hiddenfields['icqnumber']) or $isadmin)) {
01479290
DC
432 $userdetails['icq'] = $user->icq;
433 }
434
ad7612f5 435 if (in_array('skype', $userfields) && $user->skype && (!isset($hiddenfields['skypeid']) or $isadmin)) {
01479290
DC
436 $userdetails['skype'] = $user->skype;
437 }
ad7612f5 438 if (in_array('yahoo', $userfields) && $user->yahoo && (!isset($hiddenfields['yahooid']) or $isadmin)) {
01479290
DC
439 $userdetails['yahoo'] = $user->yahoo;
440 }
ad7612f5 441 if (in_array('aim', $userfields) && $user->aim && (!isset($hiddenfields['aimid']) or $isadmin)) {
01479290
DC
442 $userdetails['aim'] = $user->aim;
443 }
ad7612f5 444 if (in_array('msn', $userfields) && $user->msn && (!isset($hiddenfields['msnid']) or $isadmin)) {
01479290
DC
445 $userdetails['msn'] = $user->msn;
446 }
511db621 447 if (in_array('suspended', $userfields) && (!isset($hiddenfields['suspended']) or $isadmin)) {
84900149 448 $userdetails['suspended'] = (bool)$user->suspended;
511db621 449 }
01479290 450
ad7612f5 451 if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
01479290
DC
452 if ($user->firstaccess) {
453 $userdetails['firstaccess'] = $user->firstaccess;
454 } else {
455 $userdetails['firstaccess'] = 0;
456 }
457 }
ad7612f5 458 if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
01479290
DC
459 if ($user->lastaccess) {
460 $userdetails['lastaccess'] = $user->lastaccess;
461 } else {
462 $userdetails['lastaccess'] = 0;
463 }
464 }
465
565f3f25
DB
466 if (in_array('email', $userfields) && (
467 $currentuser
468 or (!isset($hiddenfields['email']) and (
469 $user->maildisplay == core_user::MAILDISPLAY_EVERYONE
470 or ($user->maildisplay == core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY and enrol_sharing_course($user, $USER))
471 or $canviewuseremail // TODO: Deprecate/remove for MDL-37479.
472 ))
473 or in_array('email', $showuseridentityfields)
474 )) {
90e30b96 475 $userdetails['email'] = $user->email;
01479290
DC
476 }
477
c4e868d5 478 if (in_array('interests', $userfields)) {
e11d7380 479 $interests = core_tag_tag::get_item_tags_array('core', 'user', $user->id, core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
c4e868d5
MG
480 if ($interests) {
481 $userdetails['interests'] = join(', ', $interests);
01479290
DC
482 }
483 }
484
a2ed6e69 485 // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
07d37084
DM
486 if (in_array('idnumber', $userfields) && $user->idnumber) {
487 if (in_array('idnumber', $showuseridentityfields) or $currentuser or
488 has_capability('moodle/user:viewalldetails', $context)) {
3a3f3b22
CW
489 $userdetails['idnumber'] = $user->idnumber;
490 }
48a7b182 491 }
07d37084
DM
492 if (in_array('institution', $userfields) && $user->institution) {
493 if (in_array('institution', $showuseridentityfields) or $currentuser or
494 has_capability('moodle/user:viewalldetails', $context)) {
01479290
DC
495 $userdetails['institution'] = $user->institution;
496 }
48a7b182 497 }
07d37084
DM
498 // Isset because it's ok to have department 0.
499 if (in_array('department', $userfields) && isset($user->department)) {
500 if (in_array('department', $showuseridentityfields) or $currentuser or
501 has_capability('moodle/user:viewalldetails', $context)) {
01479290
DC
502 $userdetails['department'] = $user->department;
503 }
504 }
505
ad7612f5 506 if (in_array('roles', $userfields) && !empty($course)) {
a2ed6e69 507 // Not a big secret.
01479290
DC
508 $roles = get_user_roles($context, $user->id, false);
509 $userdetails['roles'] = array();
510 foreach ($roles as $role) {
511 $userdetails['roles'][] = array(
512 'roleid' => $role->roleid,
513 'name' => $role->name,
514 'shortname' => $role->shortname,
515 'sortorder' => $role->sortorder
516 );
517 }
518 }
519
a2ed6e69 520 // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
ad7612f5 521 if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
93ce0e82
JM
522 $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
523 'g.id, g.name,g.description,g.descriptionformat');
01479290
DC
524 $userdetails['groups'] = array();
525 foreach ($usergroups as $group) {
93ce0e82
JM
526 list($group->description, $group->descriptionformat) =
527 external_format_text($group->description, $group->descriptionformat,
528 $context->id, 'group', 'description', $group->id);
a2ed6e69
SH
529 $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
530 'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
01479290
DC
531 }
532 }
a2ed6e69 533 // List of courses where the user is enrolled.
ad7612f5 534 if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
01479290
DC
535 $enrolledcourses = array();
536 if ($mycourses = enrol_get_users_courses($user->id, true)) {
537 foreach ($mycourses as $mycourse) {
538 if ($mycourse->category) {
43731030 539 $coursecontext = context_course::instance($mycourse->id);
01479290
DC
540 $enrolledcourse = array();
541 $enrolledcourse['id'] = $mycourse->id;
43ff71a0 542 $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
8ebbb06a 543 $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
01479290
DC
544 $enrolledcourses[] = $enrolledcourse;
545 }
546 }
547 $userdetails['enrolledcourses'] = $enrolledcourses;
548 }
549 }
550
a2ed6e69 551 // User preferences.
ad7612f5 552 if (in_array('preferences', $userfields) && $currentuser) {
01479290
DC
553 $preferences = array();
554 $userpreferences = get_user_preferences();
a2ed6e69 555 foreach ($userpreferences as $prefname => $prefvalue) {
01479290 556 $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
a2ed6e69
SH
557 }
558 $userdetails['preferences'] = $preferences;
01479290 559 }
ad7612f5 560
b80caca1
DTR
561 if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
562 $extrafields = ['auth', 'confirmed', 'lang', 'theme', 'timezone', 'mailformat'];
563 foreach ($extrafields as $extrafield) {
564 if (in_array($extrafield, $userfields) && isset($user->$extrafield)) {
565 $userdetails[$extrafield] = $user->$extrafield;
566 }
567 }
568 }
569
6db24235
JL
570 // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
571 if (isset($userdetails['lang'])) {
572 $userdetails['lang'] = clean_param($userdetails['lang'], PARAM_LANG);
573 }
574 if (isset($userdetails['theme'])) {
575 $userdetails['theme'] = clean_param($userdetails['theme'], PARAM_THEME);
576 }
577
01479290
DC
578 return $userdetails;
579}
580
86477112
FS
581/**
582 * Tries to obtain user details, either recurring directly to the user's system profile
c70b9853 583 * or through one of the user's course enrollments (course profile).
86477112 584 *
a2ed6e69 585 * @param stdClass $user The user.
c70b9853 586 * @return array if unsuccessful or the allowed user details.
86477112
FS
587 */
588function user_get_user_details_courses($user) {
589 global $USER;
590 $userdetails = null;
591
a2ed6e69 592 // Get the courses that the user is enrolled in (only active).
86477112
FS
593 $courses = enrol_get_users_courses($user->id, true);
594
c70b9853
JM
595 $systemprofile = false;
596 if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
597 $systemprofile = true;
598 }
86477112 599
c70b9853 600 // Try using system profile.
86477112
FS
601 if ($systemprofile) {
602 $userdetails = user_get_user_details($user, null);
603 } else {
c70b9853 604 // Try through course profile.
86477112 605 foreach ($courses as $course) {
854859e1 606 if (can_view_user_details_cap($user, $course) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
86477112
FS
607 $userdetails = user_get_user_details($user, $course);
608 }
609 }
610 }
611
612 return $userdetails;
613}
614
615/**
c70b9853
JM
616 * Check if $USER have the necessary capabilities to obtain user details.
617 *
a2ed6e69
SH
618 * @param stdClass $user
619 * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
c70b9853 620 * @return bool true if $USER can view user details.
86477112
FS
621 */
622function can_view_user_details_cap($user, $course = null) {
c70b9853 623 // Check $USER has the capability to view the user details at user context.
bea86e84 624 $usercontext = context_user::instance($user->id);
c70b9853
JM
625 $result = has_capability('moodle/user:viewdetails', $usercontext);
626 // Otherwise can $USER see them at course context.
627 if (!$result && !empty($course)) {
bea86e84 628 $context = context_course::instance($course->id);
c70b9853 629 $result = has_capability('moodle/user:viewdetails', $context);
86477112
FS
630 }
631 return $result;
632}
633
b1627a92
DC
634/**
635 * Return a list of page types
636 * @param string $pagetype current page type
637 * @param stdClass $parentcontext Block's parent context
638 * @param stdClass $currentcontext Current context of block
a2ed6e69 639 * @return array
b1627a92 640 */
b38e2e28 641function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
a2ed6e69 642 return array('user-profile' => get_string('page-user-profile', 'pagetype'));
d6731600 643}
52dc1de7
AA
644
645/**
646 * Count the number of failed login attempts for the given user, since last successful login.
647 *
648 * @param int|stdclass $user user id or object.
649 * @param bool $reset Resets failed login count, if set to true.
650 *
651 * @return int number of failed login attempts since the last successful login.
652 */
653function user_count_login_failures($user, $reset = true) {
654 global $DB;
655
656 if (!is_object($user)) {
657 $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
658 }
659 if ($user->deleted) {
660 // Deleted user, nothing to do.
661 return 0;
662 }
663 $count = get_user_preferences('login_failed_count_since_success', 0, $user);
664 if ($reset) {
665 set_user_preference('login_failed_count_since_success', 0, $user);
666 }
667 return $count;
668}
669
b865a3e2 670/**
22893d6f
JC
671 * Converts a string into a flat array of menu items, where each menu items is a
672 * stdClass with fields type, url, title, pix, and imgsrc.
b865a3e2
JC
673 *
674 * @param string $text the menu items definition
675 * @param moodle_page $page the current page
676 * @return array
677 */
678function user_convert_text_to_menu_items($text, $page) {
679 global $OUTPUT, $CFG;
680
681 $lines = explode("\n", $text);
682 $items = array();
683 $lastchild = null;
684 $lastdepth = null;
685 $lastsort = 0;
088fcf90 686 $children = array();
b865a3e2
JC
687 foreach ($lines as $line) {
688 $line = trim($line);
689 $bits = explode('|', $line, 3);
22893d6f
JC
690 $itemtype = 'link';
691 if (preg_match("/^#+$/", $line)) {
692 $itemtype = 'divider';
693 } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
b865a3e2
JC
694 // Every item must have a name to be valid.
695 continue;
696 } else {
697 $bits[0] = ltrim($bits[0], '-');
698 }
699
700 // Create the child.
701 $child = new stdClass();
22893d6f
JC
702 $child->itemtype = $itemtype;
703 if ($itemtype === 'divider') {
704 // Add the divider to the list of children and skip link
705 // processing.
706 $children[] = $child;
707 continue;
708 }
b865a3e2
JC
709
710 // Name processing.
711 $namebits = explode(',', $bits[0], 2);
712 if (count($namebits) == 2) {
06c27531
AN
713 // Check the validity of the identifier part of the string.
714 if (clean_param($namebits[0], PARAM_STRINGID) !== '') {
715 // Treat this as a language string.
716 $child->title = get_string($namebits[0], $namebits[1]);
d7dbef73 717 $child->titleidentifier = implode(',', $namebits);
06c27531
AN
718 }
719 }
720 if (empty($child->title)) {
b865a3e2
JC
721 // Use it as is, don't even clean it.
722 $child->title = $bits[0];
d7dbef73 723 $child->titleidentifier = str_replace(" ", "-", $bits[0]);
b865a3e2
JC
724 }
725
726 // URL processing.
727 if (!array_key_exists(1, $bits) or empty($bits[1])) {
22893d6f 728 // Set the url to null, and set the itemtype to invalid.
b865a3e2 729 $bits[1] = null;
22893d6f 730 $child->itemtype = "invalid";
b865a3e2 731 } else {
4887d152 732 // Nasty hack to replace the grades with the direct url.
c9451960
AG
733 if (strpos($bits[1], '/grade/report/mygrades.php') !== false) {
734 $bits[1] = user_mygrades_url();
735 }
736
b865a3e2
JC
737 // Make sure the url is a moodle url.
738 $bits[1] = new moodle_url(trim($bits[1]));
739 }
740 $child->url = $bits[1];
741
742 // PIX processing.
743 $pixpath = "t/edit";
744 if (!array_key_exists(2, $bits) or empty($bits[2])) {
745 // Use the default.
746 $child->pix = $pixpath;
747 } else {
748 // Check for the specified image existing.
749 $pixpath = "t/" . $bits[2];
750 if ($page->theme->resolve_image_location($pixpath, 'moodle', true)) {
751 // Use the image.
752 $child->pix = $pixpath;
753 } else {
754 // Treat it like a URL.
755 $child->pix = null;
756 $child->imgsrc = $bits[2];
757 }
758 }
759
760 // Add this child to the list of children.
761 $children[] = $child;
762 }
763 return $children;
764}
765
6da0e4cf
JC
766/**
767 * Get a list of essential user navigation items.
768 *
769 * @param stdclass $user user object.
770 * @param moodle_page $page page object.
9dcd5035
DB
771 * @param array $options associative array.
772 * options are:
773 * - avatarsize=35 (size of avatar image)
6da0e4cf
JC
774 * @return stdClass $returnobj navigation information object, where:
775 *
776 * $returnobj->navitems array array of links where each link is a
777 * stdClass with fields url, title, and
778 * pix
779 * $returnobj->metadata array array of useful user metadata to be
780 * used when constructing navigation;
781 * fields include:
782 *
783 * ROLE FIELDS
784 * asotherrole bool whether viewing as another role
785 * rolename string name of the role
786 *
787 * USER FIELDS
788 * These fields are for the currently-logged in user, or for
789 * the user that the real user is currently logged in as.
790 *
791 * userid int the id of the user in question
792 * userfullname string the user's full name
793 * userprofileurl moodle_url the url of the user's profile
794 * useravatar string a HTML fragment - the rendered
795 * user_picture for this user
796 * userloginfail string an error string denoting the number
797 * of login failures since last login
798 *
799 * "REAL USER" FIELDS
800 * These fields are for when asotheruser is true, and
801 * correspond to the underlying "real user".
802 *
803 * asotheruser bool whether viewing as another user
804 * realuserid int the id of the user in question
805 * realuserfullname string the user's full name
806 * realuserprofileurl moodle_url the url of the user's profile
807 * realuseravatar string a HTML fragment - the rendered
808 * user_picture for this user
809 *
810 * MNET PROVIDER FIELDS
811 * asmnetuser bool whether viewing as a user from an
812 * MNet provider
813 * mnetidprovidername string name of the MNet provider
814 * mnetidproviderwwwroot string URL of the MNet provider
815 */
c68b3206 816function user_get_user_navigation_info($user, $page, $options = array()) {
6da0e4cf
JC
817 global $OUTPUT, $DB, $SESSION, $CFG;
818
819 $returnobject = new stdClass();
820 $returnobject->navitems = array();
821 $returnobject->metadata = array();
822
823 $course = $page->course;
824
825 // Query the environment.
826 $context = context_course::instance($course->id);
827
828 // Get basic user metadata.
829 $returnobject->metadata['userid'] = $user->id;
830 $returnobject->metadata['userfullname'] = fullname($user, true);
831 $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
832 'id' => $user->id
833 ));
9dcd5035
DB
834
835 $avataroptions = array('link' => false, 'visibletoscreenreaders' => false);
836 if (!empty($options['avatarsize'])) {
837 $avataroptions['size'] = $options['avatarsize'];
838 }
6da0e4cf 839 $returnobject->metadata['useravatar'] = $OUTPUT->user_picture (
9dcd5035 840 $user, $avataroptions
6da0e4cf
JC
841 );
842 // Build a list of items for a regular user.
843
844 // Query MNet status.
845 if ($returnobject->metadata['asmnetuser'] = is_mnet_remote_user($user)) {
846 $mnetidprovider = $DB->get_record('mnet_host', array('id' => $user->mnethostid));
847 $returnobject->metadata['mnetidprovidername'] = $mnetidprovider->name;
848 $returnobject->metadata['mnetidproviderwwwroot'] = $mnetidprovider->wwwroot;
849 }
850
851 // Did the user just log in?
852 if (isset($SESSION->justloggedin)) {
853 // Don't unset this flag as login_info still needs it.
854 if (!empty($CFG->displayloginfailures)) {
fa30dd8c
JD
855 // Don't reset the count either, as login_info() still needs it too.
856 if ($count = user_count_login_failures($user, false)) {
6da0e4cf
JC
857
858 // Get login failures string.
859 $a = new stdClass();
860 $a->attempts = html_writer::tag('span', $count, array('class' => 'value'));
861 $returnobject->metadata['userloginfail'] =
862 get_string('failedloginattempts', '', $a);
863
864 }
865 }
866 }
867
81d7de1a 868 // Links: Dashboard.
6da0e4cf 869 $myhome = new stdClass();
22893d6f 870 $myhome->itemtype = 'link';
6da0e4cf
JC
871 $myhome->url = new moodle_url('/my/');
872 $myhome->title = get_string('mymoodle', 'admin');
d7dbef73 873 $myhome->titleidentifier = 'mymoodle,admin';
f3dacc38 874 $myhome->pix = "i/dashboard";
6da0e4cf
JC
875 $returnobject->navitems[] = $myhome;
876
877 // Links: My Profile.
878 $myprofile = new stdClass();
22893d6f 879 $myprofile->itemtype = 'link';
6da0e4cf 880 $myprofile->url = new moodle_url('/user/profile.php', array('id' => $user->id));
4887d152 881 $myprofile->title = get_string('profile');
d7dbef73 882 $myprofile->titleidentifier = 'profile,moodle';
6da0e4cf
JC
883 $myprofile->pix = "i/user";
884 $returnobject->navitems[] = $myprofile;
885
6da0e4cf 886 $returnobject->metadata['asotherrole'] = false;
6da0e4cf 887
70b03eff 888 // Before we add the last items (usually a logout + switch role link), add any
7095f5be
DW
889 // custom-defined items.
890 $customitems = user_convert_text_to_menu_items($CFG->customusermenuitems, $page);
891 foreach ($customitems as $item) {
892 $returnobject->navitems[] = $item;
6da0e4cf
JC
893 }
894
70b03eff 895
6da0e4cf
JC
896 if ($returnobject->metadata['asotheruser'] = \core\session\manager::is_loggedinas()) {
897 $realuser = \core\session\manager::get_realuser();
898
899 // Save values for the real user, as $user will be full of data for the
900 // user the user is disguised as.
901 $returnobject->metadata['realuserid'] = $realuser->id;
902 $returnobject->metadata['realuserfullname'] = fullname($realuser, true);
903 $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
904 'id' => $realuser->id
905 ));
c68b3206 906 $returnobject->metadata['realuseravatar'] = $OUTPUT->user_picture($realuser, $avataroptions);
6da0e4cf
JC
907
908 // Build a user-revert link.
909 $userrevert = new stdClass();
22893d6f 910 $userrevert->itemtype = 'link';
6da0e4cf
JC
911 $userrevert->url = new moodle_url('/course/loginas.php', array(
912 'id' => $course->id,
913 'sesskey' => sesskey()
914 ));
915 $userrevert->pix = "a/logout";
916 $userrevert->title = get_string('logout');
d7dbef73 917 $userrevert->titleidentifier = 'logout,moodle';
7095f5be 918 $returnobject->navitems[] = $userrevert;
6da0e4cf 919
7095f5be 920 } else {
6da0e4cf 921
6da0e4cf
JC
922 // Build a logout link.
923 $logout = new stdClass();
22893d6f 924 $logout->itemtype = 'link';
6da0e4cf
JC
925 $logout->url = new moodle_url('/login/logout.php', array('sesskey' => sesskey()));
926 $logout->pix = "a/logout";
927 $logout->title = get_string('logout');
d7dbef73 928 $logout->titleidentifier = 'logout,moodle';
7095f5be 929 $returnobject->navitems[] = $logout;
6da0e4cf
JC
930 }
931
70b03eff
DW
932 if (is_role_switched($course->id)) {
933 if ($role = $DB->get_record('role', array('id' => $user->access['rsw'][$context->path]))) {
934 // Build role-return link instead of logout link.
935 $rolereturn = new stdClass();
936 $rolereturn->itemtype = 'link';
937 $rolereturn->url = new moodle_url('/course/switchrole.php', array(
938 'id' => $course->id,
939 'sesskey' => sesskey(),
940 'switchrole' => 0,
941 'returnurl' => $page->url->out_as_local_url(false)
942 ));
943 $rolereturn->pix = "a/logout";
944 $rolereturn->title = get_string('switchrolereturn');
945 $rolereturn->titleidentifier = 'switchrolereturn,moodle';
946 $returnobject->navitems[] = $rolereturn;
947
948 $returnobject->metadata['asotherrole'] = true;
949 $returnobject->metadata['rolename'] = role_get_name($role, $context);
b865a3e2 950
70b03eff
DW
951 }
952 } else {
7dba450f
DW
953 // Build switch role link.
954 $roles = get_switchable_roles($context);
955 if (is_array($roles) && (count($roles) > 0)) {
956 $switchrole = new stdClass();
957 $switchrole->itemtype = 'link';
958 $switchrole->url = new moodle_url('/course/switchrole.php', array(
959 'id' => $course->id,
960 'switchrole' => -1,
961 'returnurl' => $page->url->out_as_local_url(false)
962 ));
963 $switchrole->pix = "i/switchrole";
964 $switchrole->title = get_string('switchroleto');
965 $switchrole->titleidentifier = 'switchroleto,moodle';
966 $returnobject->navitems[] = $switchrole;
967 }
6da0e4cf
JC
968 }
969
970 return $returnobject;
1d658535
PS
971}
972
973/**
974 * Add password to the list of used hashes for this user.
975 *
976 * This is supposed to be used from:
977 * 1/ change own password form
978 * 2/ password reset process
979 * 3/ user signup in auth plugins if password changing supported
980 *
981 * @param int $userid user id
982 * @param string $password plaintext password
983 * @return void
984 */
985function user_add_password_history($userid, $password) {
986 global $CFG, $DB;
1d658535
PS
987
988 if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
989 return;
990 }
991
992 // Note: this is using separate code form normal password hashing because
993 // we need to have this under control in the future. Also the auth
994 // plugin might not store the passwords locally at all.
995
996 $record = new stdClass();
997 $record->userid = $userid;
998 $record->hash = password_hash($password, PASSWORD_DEFAULT);
999 $record->timecreated = time();
1000 $DB->insert_record('user_password_history', $record);
1001
1002 $i = 0;
1003 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
1004 foreach ($records as $record) {
1005 $i++;
1006 if ($i > $CFG->passwordreuselimit) {
1007 $DB->delete_records('user_password_history', array('id' => $record->id));
1008 }
1009 }
1010}
1011
1012/**
1013 * Was this password used before on change or reset password page?
1014 *
1015 * The $CFG->passwordreuselimit setting determines
1016 * how many times different password needs to be used
1017 * before allowing previously used password again.
1018 *
1019 * @param int $userid user id
1020 * @param string $password plaintext password
1021 * @return bool true if password reused
1022 */
1023function user_is_previously_used_password($userid, $password) {
1024 global $CFG, $DB;
1d658535
PS
1025
1026 if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) {
1027 return false;
1028 }
1029
1030 $reused = false;
1031
1032 $i = 0;
1033 $records = $DB->get_records('user_password_history', array('userid' => $userid), 'timecreated DESC, id DESC');
1034 foreach ($records as $record) {
1035 $i++;
1036 if ($i > $CFG->passwordreuselimit) {
1037 $DB->delete_records('user_password_history', array('id' => $record->id));
1038 continue;
1039 }
1040 // NOTE: this is slow but we cannot compare the hashes directly any more.
1041 if (password_verify($password, $record->hash)) {
1042 $reused = true;
1043 }
1044 }
1045
1046 return $reused;
1047}
3221718e
JL
1048
1049/**
1050 * Remove a user device from the Moodle database (for PUSH notifications usually).
1051 *
1052 * @param string $uuid The device UUID.
1053 * @param string $appid The app id. If empty all the devices matching the UUID for the user will be removed.
1054 * @return bool true if removed, false if the device didn't exists in the database
1055 * @since Moodle 2.9
1056 */
1057function user_remove_user_device($uuid, $appid = "") {
1058 global $DB, $USER;
1059
1060 $conditions = array('uuid' => $uuid, 'userid' => $USER->id);
1061 if (!empty($appid)) {
1062 $conditions['appid'] = $appid;
1063 }
1064
1065 if (!$DB->count_records('user_devices', $conditions)) {
1066 return false;
1067 }
1068
1069 $DB->delete_records('user_devices', $conditions);
1070
1071 return true;
1072}
0ff203b6
JL
1073
1074/**
1075 * Trigger user_list_viewed event.
1076 *
1077 * @param stdClass $course course object
1078 * @param stdClass $context course context object
1079 * @since Moodle 2.9
1080 */
1081function user_list_view($course, $context) {
1082
1083 $event = \core\event\user_list_viewed::create(array(
1084 'objectid' => $course->id,
1085 'courseid' => $course->id,
1086 'context' => $context,
1087 'other' => array(
1088 'courseshortname' => $course->shortname,
1089 'coursefullname' => $course->fullname
1090 )
1091 ));
1092 $event->trigger();
1093}
c9451960
AG
1094
1095/**
4887d152 1096 * Returns the url to use for the "Grades" link in the user navigation.
c9451960
AG
1097 *
1098 * @param int $userid The user's ID.
1099 * @param int $courseid The course ID if available.
4887d152 1100 * @return mixed A URL to be directed to for "Grades".
c9451960
AG
1101 */
1102function user_mygrades_url($userid = null, $courseid = SITEID) {
1103 global $CFG, $USER;
1104 $url = null;
1105 if (isset($CFG->grade_mygrades_report) && $CFG->grade_mygrades_report != 'external') {
1106 if (isset($userid) && $USER->id != $userid) {
1107 // Send to the gradebook report.
1108 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report . '/index.php',
1109 array('id' => $courseid, 'userid' => $userid));
1110 } else {
1111 $url = new moodle_url('/grade/report/' . $CFG->grade_mygrades_report . '/index.php');
1112 }
1113 } else if (isset($CFG->grade_mygrades_report) && $CFG->grade_mygrades_report == 'external'
1114 && !empty($CFG->gradereport_mygradeurl)) {
1115 $url = $CFG->gradereport_mygradeurl;
1116 } else {
1117 $url = $CFG->wwwroot;
1118 }
1119 return $url;
1120}
66a43cd5
AG
1121
1122/**
eb01e70b
JD
1123 * Check if the current user has permission to view details of the supplied user.
1124 *
1125 * This function supports two modes:
1126 * If the optional $course param is omitted, then this function finds all shared courses and checks whether the current user has
1127 * permission in any of them, returning true if so.
1128 * If the $course param is provided, then this function checks permissions in ONLY that course.
66a43cd5
AG
1129 *
1130 * @param object $user The other user's details.
eb01e70b 1131 * @param object $course if provided, only check permissions in this course.
66a43cd5
AG
1132 * @param context $usercontext The user context if available.
1133 * @return bool true for ability to view this user, else false.
1134 */
1135function user_can_view_profile($user, $course = null, $usercontext = null) {
1136 global $USER, $CFG;
1137
1138 if ($user->deleted) {
1139 return false;
1140 }
1141
eb01e70b 1142 // Do we need to be logged in?
58d85af2 1143 if (empty($CFG->forceloginforprofiles)) {
66a43cd5 1144 return true;
58d85af2
AA
1145 } else {
1146 if (!isloggedin() || isguestuser()) {
1147 // User is not logged in and forceloginforprofile is set, we need to return now.
1148 return false;
1149 }
66a43cd5
AG
1150 }
1151
eb01e70b 1152 // Current user can always view their profile.
58d85af2 1153 if ($USER->id == $user->id) {
66a43cd5
AG
1154 return true;
1155 }
1156
bef86c66 1157 // Use callbacks so that (primarily) local plugins can prevent or allow profile access.
1158 $forceallow = false;
1159 $plugintypes = get_plugins_with_function('control_view_profile');
1160 foreach ($plugintypes as $plugins) {
1161 foreach ($plugins as $pluginfunction) {
1162 $result = $pluginfunction($user, $course, $usercontext);
1163 switch ($result) {
1164 case core_user::VIEWPROFILE_DO_NOT_PREVENT:
1165 // If the plugin doesn't stop access, just continue to next plugin or use
1166 // default behaviour.
1167 break;
1168 case core_user::VIEWPROFILE_FORCE_ALLOW:
1169 // Record that we are definitely going to allow it (unless another plugin
1170 // returns _PREVENT).
1171 $forceallow = true;
1172 break;
1173 case core_user::VIEWPROFILE_PREVENT:
1174 // If any plugin returns PREVENT then we return false, regardless of what
1175 // other plugins said.
1176 return false;
1177 }
1178 }
1179 }
1180 if ($forceallow) {
1181 return true;
1182 }
1183
eb01e70b 1184 // Course contacts have visible profiles always.
66a43cd5
AG
1185 if (has_coursecontact_role($user->id)) {
1186 return true;
1187 }
1188
eb01e70b 1189 // If we're only checking the capabilities in the single provided course.
66a43cd5 1190 if (isset($course)) {
eb01e70b
JD
1191 // Confirm that $user is enrolled in the $course we're checking.
1192 if (is_enrolled(context_course::instance($course->id), $user)) {
1193 $userscourses = array($course);
1194 }
66a43cd5 1195 } else {
eb01e70b
JD
1196 // Else we're checking whether the current user can view $user's profile anywhere, so check user context first.
1197 if (empty($usercontext)) {
1198 $usercontext = context_user::instance($user->id);
1199 }
1200 if (has_capability('moodle/user:viewdetails', $usercontext) || has_capability('moodle/user:viewalldetails', $usercontext)) {
1201 return true;
1202 }
067accce
JD
1203 // This returns context information, so we can preload below.
1204 $userscourses = enrol_get_all_users_courses($user->id);
66a43cd5 1205 }
3e40dc8c 1206
067accce 1207 if (empty($userscourses)) {
3e40dc8c
AA
1208 return false;
1209 }
1210
067accce
JD
1211 foreach ($userscourses as $userscourse) {
1212 context_helper::preload_from_record($userscourse);
1213 $coursecontext = context_course::instance($userscourse->id);
eb01e70b
JD
1214 if (has_capability('moodle/user:viewdetails', $coursecontext) ||
1215 has_capability('moodle/user:viewalldetails', $coursecontext)) {
067accce 1216 if (!groups_user_groups_visible($userscourse, $user->id)) {
66a43cd5
AG
1217 // Not a member of the same group.
1218 continue;
1219 }
1220 return true;
1221 }
1222 }
1223 return false;
1224}
c4e868d5
MG
1225
1226/**
1227 * Returns users tagged with a specified tag.
1228 *
1229 * @param core_tag_tag $tag
1230 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
1231 * are displayed on the page and the per-page limit may be bigger
1232 * @param int $fromctx context id where the link was displayed, may be used by callbacks
1233 * to display items in the same context first
1234 * @param int $ctx context id where to search for records
1235 * @param bool $rec search in subcontexts as well
1236 * @param int $page 0-based number of page being displayed
1237 * @return \core_tag\output\tagindex
1238 */
1239function user_get_tagged_users($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) {
1240 global $PAGE;
1241
1242 if ($ctx && $ctx != context_system::instance()->id) {
1243 $usercount = 0;
1244 } else {
1245 // Users can only be displayed in system context.
1246 $usercount = $tag->count_tagged_items('core', 'user',
1247 'it.deleted=:notdeleted', array('notdeleted' => 0));
1248 }
1249 $perpage = $exclusivemode ? 24 : 5;
1250 $content = '';
1251 $totalpages = ceil($usercount / $perpage);
1252
1253 if ($usercount) {
1254 $userlist = $tag->get_tagged_items('core', 'user', $page * $perpage, $perpage,
1255 'it.deleted=:notdeleted', array('notdeleted' => 0));
1256 $renderer = $PAGE->get_renderer('core', 'user');
1257 $content .= $renderer->user_list($userlist, $exclusivemode);
1258 }
1259
1260 return new core_tag\output\tagindex($tag, 'core', 'user', $content,
1261 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
1262}
bc47b706
MN
1263
1264/**
1265 * Returns the SQL used by the participants table.
1266 *
1267 * @param int $courseid The course id
1268 * @param int $groupid The groupid, 0 means all groups
1269 * @param int $accesssince The time since last access, 0 means any time
1270 * @param int $roleid The role id, 0 means all roles
9651e491
JP
1271 * @param int $enrolid The enrolment id, 0 means all enrolment methods will be returned.
1272 * @param int $statusid The user enrolment status, -1 means all enrolments regardless of the status will be returned, if allowed.
1273 * @param string|array $search The search that was performed, empty means perform no search
bc47b706
MN
1274 * @param string $additionalwhere Any additional SQL to add to where
1275 * @param array $additionalparams The additional params
1276 * @return array
1277 */
9651e491
JP
1278function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
1279 $search = '', $additionalwhere = '', $additionalparams = array()) {
4bcb0a9a 1280 global $DB, $USER;
bc47b706
MN
1281
1282 // Get the context.
1283 $context = \context_course::instance($courseid, MUST_EXIST);
1284
1285 $isfrontpage = ($courseid == SITEID);
1286
9651e491
JP
1287 // Default filter settings. We only show active by default, especially if the user has no capability to review enrolments.
1288 $onlyactive = true;
1289 $onlysuspended = false;
1290 if (has_capability('moodle/course:enrolreview', $context)) {
1291 switch ($statusid) {
1292 case ENROL_USER_ACTIVE:
1293 // Nothing to do here.
1294 break;
1295 case ENROL_USER_SUSPENDED:
1296 $onlyactive = false;
1297 $onlysuspended = true;
1298 break;
1299 default:
1300 // If the user has capability to review user enrolments, but statusid is set to -1, set $onlyactive to false.
1301 $onlyactive = false;
1302 break;
1303 }
1304 }
1305
1306 list($esql, $params) = get_enrolled_sql($context, null, $groupid, $onlyactive, $onlysuspended, $enrolid);
bc47b706
MN
1307
1308 $joins = array('FROM {user} u');
1309 $wheres = array();
1310
aa949cb2 1311 $userfields = get_extra_user_fields($context);
a9991533 1312 $userfieldssql = user_picture::fields('u', $userfields);
bc47b706
MN
1313
1314 if ($isfrontpage) {
a9991533 1315 $select = "SELECT $userfieldssql, u.lastaccess";
bc47b706
MN
1316 $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually.
1317 if ($accesssince) {
1318 $wheres[] = user_get_user_lastaccess_sql($accesssince);
1319 }
1320 } else {
a9991533 1321 $select = "SELECT $userfieldssql, COALESCE(ul.timeaccess, 0) AS lastaccess";
bc47b706
MN
1322 $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only.
1323 // Not everybody has accessed the course yet.
1324 $joins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)';
1325 $params['courseid'] = $courseid;
1326 if ($accesssince) {
1327 $wheres[] = user_get_course_lastaccess_sql($accesssince);
1328 }
1329 }
1330
1331 // Performance hacks - we preload user contexts together with accounts.
1332 $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1333 $ccjoin = 'LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)';
1334 $params['contextlevel'] = CONTEXT_USER;
1335 $select .= $ccselect;
1336 $joins[] = $ccjoin;
1337
1338 // Limit list to users with some role only.
1339 if ($roleid) {
1340 // We want to query both the current context and parent contexts.
1341 list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
1342 SQL_PARAMS_NAMED, 'relatedctx');
1343
1344 $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
1345 $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
1346 }
1347
1348 if (!empty($search)) {
9651e491
JP
1349 if (!is_array($search)) {
1350 $search = [$search];
1351 }
1352 foreach ($search as $index => $keyword) {
1353 $searchkey1 = 'search' . $index . '1';
1354 $searchkey2 = 'search' . $index . '2';
1355 $searchkey3 = 'search' . $index . '3';
358b10a3
IS
1356 $searchkey4 = 'search' . $index . '4';
1357 $searchkey5 = 'search' . $index . '5';
1358 $searchkey6 = 'search' . $index . '6';
1359 $searchkey7 = 'search' . $index . '7';
4bcb0a9a
SA
1360
1361 $conditions = array();
1362 // Search by fullname.
9651e491 1363 $fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
4bcb0a9a
SA
1364 $conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
1365
1366 // Search by email.
1367 $email = $DB->sql_like('email', ':' . $searchkey2, false, false);
1368 if (!in_array('email', $userfields)) {
1369 $maildisplay = 'maildisplay' . $index;
1370 $userid1 = 'userid' . $index . '1';
1371 // Prevent users who hide their email address from being found by others
1372 // who aren't allowed to see hidden email addresses.
1373 $email = "(". $email ." AND (" .
1374 "u.maildisplay <> :$maildisplay " .
1375 "OR u.id = :$userid1". // User can always find himself.
1376 "))";
1377 $params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
1378 $params[$userid1] = $USER->id;
1379 }
1380 $conditions[] = $email;
1381
1382 // Search by idnumber.
1383 $idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
1384 if (!in_array('idnumber', $userfields)) {
1385 $userid2 = 'userid' . $index . '2';
1386 // Users who aren't allowed to see idnumbers should at most find themselves
1387 // when searching for an idnumber.
1388 $idnumber = "(". $idnumber . " AND u.id = :$userid2)";
1389 $params[$userid2] = $USER->id;
1390 }
1391 $conditions[] = $idnumber;
1392
358b10a3
IS
1393 // Search by middlename.
1394 $middlename = $DB->sql_like('middlename', ':' . $searchkey4, false, false);
1395 $conditions[] = $middlename;
1396
1397 // Search by alternatename.
1398 $alternatename = $DB->sql_like('alternatename', ':' . $searchkey5, false, false);
1399 $conditions[] = $alternatename;
1400
1401 // Search by firstnamephonetic.
1402 $firstnamephonetic = $DB->sql_like('firstnamephonetic', ':' . $searchkey6, false, false);
1403 $conditions[] = $firstnamephonetic;
1404
1405 // Search by lastnamephonetic.
1406 $lastnamephonetic = $DB->sql_like('lastnamephonetic', ':' . $searchkey7, false, false);
1407 $conditions[] = $lastnamephonetic;
1408
4bcb0a9a 1409 $wheres[] = "(". implode(" OR ", $conditions) .") ";
9651e491
JP
1410 $params[$searchkey1] = "%$keyword%";
1411 $params[$searchkey2] = "%$keyword%";
1412 $params[$searchkey3] = "%$keyword%";
358b10a3
IS
1413 $params[$searchkey4] = "%$keyword%";
1414 $params[$searchkey5] = "%$keyword%";
1415 $params[$searchkey6] = "%$keyword%";
1416 $params[$searchkey7] = "%$keyword%";
9651e491 1417 }
bc47b706
MN
1418 }
1419
1420 if (!empty($additionalwhere)) {
1421 $wheres[] = $additionalwhere;
1422 $params = array_merge($params, $additionalparams);
1423 }
1424
1425 $from = implode("\n", $joins);
1426 if ($wheres) {
1427 $where = 'WHERE ' . implode(' AND ', $wheres);
1428 } else {
1429 $where = '';
1430 }
1431
1432 return array($select, $from, $where, $params);
1433}
1434
1435/**
1436 * Returns the total number of participants for a given course.
1437 *
1438 * @param int $courseid The course id
1439 * @param int $groupid The groupid, 0 means all groups
1440 * @param int $accesssince The time since last access, 0 means any time
1441 * @param int $roleid The role id, 0 means all roles
9651e491
JP
1442 * @param int $enrolid The applied filter for the user enrolment ID.
1443 * @param int $status The applied filter for the user's enrolment status.
1444 * @param string|array $search The search that was performed, empty means perform no search
bc47b706
MN
1445 * @param string $additionalwhere Any additional SQL to add to where
1446 * @param array $additionalparams The additional params
1447 * @return int
1448 */
9651e491
JP
1449function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
1450 $search = '', $additionalwhere = '', $additionalparams = array()) {
bc47b706
MN
1451 global $DB;
1452
9651e491
JP
1453 list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
1454 $statusid, $search, $additionalwhere, $additionalparams);
bc47b706
MN
1455
1456 return $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params);
1457}
1458
1459/**
1460 * Returns the participants for a given course.
1461 *
1462 * @param int $courseid The course id
1463 * @param int $groupid The group id
1464 * @param int $accesssince The time since last access
1465 * @param int $roleid The role id
9651e491
JP
1466 * @param int $enrolid The applied filter for the user enrolment ID.
1467 * @param int $status The applied filter for the user's enrolment status.
bc47b706
MN
1468 * @param string $search The search that was performed
1469 * @param string $additionalwhere Any additional SQL to add to where
1470 * @param array $additionalparams The additional params
1471 * @param string $sort The SQL sort
1472 * @param int $limitfrom return a subset of records, starting at this point (optional).
1473 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1474 * @return moodle_recordset
1475 */
9651e491
JP
1476function user_get_participants($courseid, $groupid = 0, $accesssince, $roleid, $enrolid = 0, $statusid, $search,
1477 $additionalwhere = '', $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) {
bc47b706
MN
1478 global $DB;
1479
9651e491
JP
1480 list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
1481 $statusid, $search, $additionalwhere, $additionalparams);
bc47b706
MN
1482
1483 return $DB->get_recordset_sql("$select $from $where $sort", $params, $limitfrom, $limitnum);
1484}
1485
1486/**
1487 * Returns SQL that can be used to limit a query to a period where the user last accessed a course.
1488 *
1489 * @param int $accesssince The time since last access
1490 * @param string $tableprefix
1491 * @return string
1492 */
1493function user_get_course_lastaccess_sql($accesssince = null, $tableprefix = 'ul') {
1494 if (empty($accesssince)) {
1495 return '';
1496 }
1497
1498 if ($accesssince == -1) { // Never.
1499 return $tableprefix . '.timeaccess = 0';
1500 } else {
1501 return $tableprefix . '.timeaccess != 0 AND ul.timeaccess < ' . $accesssince;
1502 }
1503}
1504
1505/**
1506 * Returns SQL that can be used to limit a query to a period where the user last accessed the system.
1507 *
1508 * @param int $accesssince The time since last access
1509 * @param string $tableprefix
1510 * @return string
1511 */
1512function user_get_user_lastaccess_sql($accesssince = null, $tableprefix = 'u') {
1513 if (empty($accesssince)) {
1514 return '';
1515 }
1516
1517 if ($accesssince == -1) { // Never.
1518 return $tableprefix . '.lastaccess = 0';
1519 } else {
1520 return $tableprefix . '.lastaccess != 0 AND u.lastaccess < ' . $accesssince;
1521 }
1522}
5d0b4765
DW
1523
1524/**
1525 * Callback for inplace editable API.
1526 *
1527 * @param string $itemtype - Only user_roles is supported.
73d0d562 1528 * @param string $itemid - Courseid and userid separated by a :
5d0b4765
DW
1529 * @param string $newvalue - json encoded list of roleids.
1530 * @return \core\output\inplace_editable
1531 */
1532function core_user_inplace_editable($itemtype, $itemid, $newvalue) {
1533 if ($itemtype === 'user_roles') {
1534 return \core_user\output\user_roles_editable::update($itemid, $newvalue);
1535 }
1536}