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