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