Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • /user/ -> lib.php (source)

    Differences Between: [Versions 310 and 311] [Versions 35 and 311] [Versions 36 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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 $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  
      98      $user->timecreated = time();
      99      $user->timemodified = $user->timecreated;
     100  
     101      // Validate user data object.
     102      $uservalidation = core_user::validate($user);
     103      if ($uservalidation !== true) {
     104          foreach ($uservalidation as $field => $message) {
     105              debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER);
     106              $user->$field = core_user::clean_field($user->$field, $field);
     107          }
     108      }
     109  
     110      // Insert the user into the database.
     111      $newuserid = $DB->insert_record('user', $user);
     112  
     113      // Create USER context for this user.
     114      $usercontext = context_user::instance($newuserid);
     115  
     116      // Update user password if necessary.
     117      if (isset($userpassword)) {
     118          // Get full database user row, in case auth is default.
     119          $newuser = $DB->get_record('user', array('id' => $newuserid));
     120          $authplugin = get_auth_plugin($newuser->auth);
     121          $authplugin->user_update_password($newuser, $userpassword);
     122      }
     123  
     124      // Trigger event If required.
     125      if ($triggerevent) {
     126          \core\event\user_created::create_from_userid($newuserid)->trigger();
     127      }
     128  
     129      // Purge the associated caches for the current user only.
     130      $presignupcache = \cache::make('core', 'presignup');
     131      $presignupcache->purge_current_user();
     132  
     133      return $newuserid;
     134  }
     135  
     136  /**
     137   * Update a user with a user object (will compare against the ID)
     138   *
     139   * @throws moodle_exception
     140   * @param stdClass $user the user to update
     141   * @param bool $updatepassword if true, authentication plugin will update password.
     142   * @param bool $triggerevent set false if user_updated event should not be triggred.
     143   *             This will not affect user_password_updated event triggering.
     144   */
     145  function user_update_user($user, $updatepassword = true, $triggerevent = true) {
     146      global $DB;
     147  
     148      // Set the timecreate field to the current time.
     149      if (!is_object($user)) {
     150          $user = (object) $user;
     151      }
     152  
     153      // Check username.
     154      if (isset($user->username)) {
     155          if ($user->username !== core_text::strtolower($user->username)) {
     156              throw new moodle_exception('usernamelowercase');
     157          } else {
     158              if ($user->username !== core_user::clean_field($user->username, 'username')) {
     159                  throw new moodle_exception('invalidusername');
     160              }
     161          }
     162      }
     163  
     164      // Unset password here, for updating later, if password update is required.
     165      if ($updatepassword && isset($user->password)) {
     166  
     167          // Check password toward the password policy.
     168          if (!check_password_policy($user->password, $errmsg, $user)) {
     169              throw new moodle_exception($errmsg);
     170          }
     171  
     172          $passwd = $user->password;
     173          unset($user->password);
     174      }
     175  
     176      // Make sure calendartype, if set, is valid.
     177      if (empty($user->calendartype)) {
     178          // Unset this variable, must be an empty string, which we do not want to update the calendartype to.
     179          unset($user->calendartype);
     180      }
     181  
     182      $user->timemodified = time();
     183  
     184      // Validate user data object.
     185      $uservalidation = core_user::validate($user);
     186      if ($uservalidation !== true) {
     187          foreach ($uservalidation as $field => $message) {
     188              debugging("The property '$field' has invalid data and has been cleaned.", DEBUG_DEVELOPER);
     189              $user->$field = core_user::clean_field($user->$field, $field);
     190          }
     191      }
     192  
     193      $DB->update_record('user', $user);
     194  
     195      if ($updatepassword) {
     196          // Get full user record.
     197          $updateduser = $DB->get_record('user', array('id' => $user->id));
     198  
     199          // If password was set, then update its hash.
     200          if (isset($passwd)) {
     201              $authplugin = get_auth_plugin($updateduser->auth);
     202              if ($authplugin->can_change_password()) {
     203                  $authplugin->user_update_password($updateduser, $passwd);
     204              }
     205          }
     206      }
     207      // Trigger event if required.
     208      if ($triggerevent) {
     209          \core\event\user_updated::create_from_userid($user->id)->trigger();
     210      }
     211  }
     212  
     213  /**
     214   * Marks user deleted in internal user database and notifies the auth plugin.
     215   * Also unenrols user from all roles and does other cleanup.
     216   *
     217   * @todo Decide if this transaction is really needed (look for internal TODO:)
     218   * @param object $user Userobject before delete    (without system magic quotes)
     219   * @return boolean success
     220   */
     221  function user_delete_user($user) {
     222      return delete_user($user);
     223  }
     224  
     225  /**
     226   * Get users by id
     227   *
     228   * @param array $userids id of users to retrieve
     229   * @return array
     230   */
     231  function user_get_users_by_id($userids) {
     232      global $DB;
     233      return $DB->get_records_list('user', 'id', $userids);
     234  }
     235  
     236  /**
     237   * Returns the list of default 'displayable' fields
     238   *
     239   * Contains database field names but also names used to generate information, such as enrolledcourses
     240   *
     241   * @return array of user fields
     242   */
     243  function user_get_default_fields() {
     244      return array( 'id', 'username', 'fullname', 'firstname', 'lastname', 'email',
     245          'address', 'phone1', 'phone2', 'department',
     246          'institution', 'interests', 'firstaccess', 'lastaccess', 'auth', 'confirmed',
     247          'idnumber', 'lang', 'theme', 'timezone', 'mailformat', 'description', 'descriptionformat',
     248          'city', 'country', 'profileimageurlsmall', 'profileimageurl', 'customfields',
     249          'groups', 'roles', 'preferences', 'enrolledcourses', 'suspended', 'lastcourseaccess'
     250      );
     251  }
     252  
     253  /**
     254   *
     255   * Give user record from mdl_user, build an array contains all user details.
     256   *
     257   * Warning: description file urls are 'webservice/pluginfile.php' is use.
     258   *          it can be changed with $CFG->moodlewstextformatlinkstoimagesfile
     259   *
     260   * @throws moodle_exception
     261   * @param stdClass $user user record from mdl_user
     262   * @param stdClass $course moodle course
     263   * @param array $userfields required fields
     264   * @return array|null
     265   */
     266  function user_get_user_details($user, $course = null, array $userfields = array()) {
     267      global $USER, $DB, $CFG, $PAGE;
     268      require_once($CFG->dirroot . "/user/profile/lib.php"); // Custom field library.
     269      require_once($CFG->dirroot . "/lib/filelib.php");      // File handling on description and friends.
     270  
     271      $defaultfields = user_get_default_fields();
     272  
     273      if (empty($userfields)) {
     274          $userfields = $defaultfields;
     275      }
     276  
     277      foreach ($userfields as $thefield) {
     278          if (!in_array($thefield, $defaultfields)) {
     279              throw new moodle_exception('invaliduserfield', 'error', '', $thefield);
     280          }
     281      }
     282  
     283      // Make sure id and fullname are included.
     284      if (!in_array('id', $userfields)) {
     285          $userfields[] = 'id';
     286      }
     287  
     288      if (!in_array('fullname', $userfields)) {
     289          $userfields[] = 'fullname';
     290      }
     291  
     292      if (!empty($course)) {
     293          $context = context_course::instance($course->id);
     294          $usercontext = context_user::instance($user->id);
     295          $canviewdetailscap = (has_capability('moodle/user:viewdetails', $context) || has_capability('moodle/user:viewdetails', $usercontext));
     296      } else {
     297          $context = context_user::instance($user->id);
     298          $usercontext = $context;
     299          $canviewdetailscap = has_capability('moodle/user:viewdetails', $usercontext);
     300      }
     301  
     302      $currentuser = ($user->id == $USER->id);
     303      $isadmin = is_siteadmin($USER);
     304  
     305      // This does not need to include custom profile fields as it is only used to check specific
     306      // fields below.
     307      $showuseridentityfields = \core_user\fields::get_identity_fields($context, false);
     308  
     309      if (!empty($course)) {
     310          $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
     311      } else {
     312          $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
     313      }
     314      $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
     315      if (!empty($course)) {
     316          $canviewuseremail = has_capability('moodle/course:useremail', $context);
     317      } else {
     318          $canviewuseremail = false;
     319      }
     320      $cannotviewdescription   = !empty($CFG->profilesforenrolledusersonly) && !$currentuser && !$DB->record_exists('role_assignments', array('userid' => $user->id));
     321      if (!empty($course)) {
     322          $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
     323      } else {
     324          $canaccessallgroups = false;
     325      }
     326  
     327      if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
     328          // Skip this user details.
     329          return null;
     330      }
     331  
     332      $userdetails = array();
     333      $userdetails['id'] = $user->id;
     334  
     335      if (in_array('username', $userfields)) {
     336          if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
     337              $userdetails['username'] = $user->username;
     338          }
     339      }
     340      if ($isadmin or $canviewfullnames) {
     341          if (in_array('firstname', $userfields)) {
     342              $userdetails['firstname'] = $user->firstname;
     343          }
     344          if (in_array('lastname', $userfields)) {
     345              $userdetails['lastname'] = $user->lastname;
     346          }
     347      }
     348      $userdetails['fullname'] = fullname($user, $canviewfullnames);
     349  
     350      if (in_array('customfields', $userfields)) {
     351          $categories = profile_get_user_fields_with_data_by_category($user->id);
     352          $userdetails['customfields'] = array();
     353          foreach ($categories as $categoryid => $fields) {
     354              foreach ($fields as $formfield) {
     355                  if ($formfield->is_visible() and !$formfield->is_empty()) {
     356  
     357                      // TODO: Part of MDL-50728, this conditional coding must be moved to
     358                      // proper profile fields API so they are self-contained.
     359                      // We only use display_data in fields that require text formatting.
     360                      if ($formfield->field->datatype == 'text' or $formfield->field->datatype == 'textarea') {
     361                          $fieldvalue = $formfield->display_data();
     362                      } else {
     363                          // Cases: datetime, checkbox and menu.
     364                          $fieldvalue = $formfield->data;
     365                      }
     366  
     367                      $userdetails['customfields'][] =
     368                          array('name' => $formfield->field->name, 'value' => $fieldvalue,
     369                              'type' => $formfield->field->datatype, 'shortname' => $formfield->field->shortname);
     370                  }
     371              }
     372          }
     373          // Unset customfields if it's empty.
     374          if (empty($userdetails['customfields'])) {
     375              unset($userdetails['customfields']);
     376          }
     377      }
     378  
     379      // Profile image.
     380      if (in_array('profileimageurl', $userfields)) {
     381          $userpicture = new user_picture($user);
     382          $userpicture->size = 1; // Size f1.
     383          $userdetails['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
     384      }
     385      if (in_array('profileimageurlsmall', $userfields)) {
     386          if (!isset($userpicture)) {
     387              $userpicture = new user_picture($user);
     388          }
     389          $userpicture->size = 0; // Size f2.
     390          $userdetails['profileimageurlsmall'] = $userpicture->get_url($PAGE)->out(false);
     391      }
     392  
     393      // Hidden user field.
     394      if ($canviewhiddenuserfields) {
     395          $hiddenfields = array();
     396          // Address, phone1 and phone2 not appears in hidden fields list but require viewhiddenfields capability
     397          // according to user/profile.php.
     398          if (!empty($user->address) && in_array('address', $userfields)) {
     399              $userdetails['address'] = $user->address;
     400          }
     401      } else {
     402          $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
     403      }
     404  
     405      if (!empty($user->phone1) && in_array('phone1', $userfields) &&
     406              (in_array('phone1', $showuseridentityfields) or $canviewhiddenuserfields)) {
     407          $userdetails['phone1'] = $user->phone1;
     408      }
     409      if (!empty($user->phone2) && in_array('phone2', $userfields) &&
     410              (in_array('phone2', $showuseridentityfields) or $canviewhiddenuserfields)) {
     411          $userdetails['phone2'] = $user->phone2;
     412      }
     413  
     414      if (isset($user->description) &&
     415          ((!isset($hiddenfields['description']) && !$cannotviewdescription) or $isadmin)) {
     416          if (in_array('description', $userfields)) {
     417              // Always return the descriptionformat if description is requested.
     418              list($userdetails['description'], $userdetails['descriptionformat']) =
     419                      external_format_text($user->description, $user->descriptionformat,
     420                              $usercontext->id, 'user', 'profile', null);
     421          }
     422      }
     423  
     424      if (in_array('country', $userfields) && (!isset($hiddenfields['country']) or $isadmin) && $user->country) {
     425          $userdetails['country'] = $user->country;
     426      }
     427  
     428      if (in_array('city', $userfields) && (!isset($hiddenfields['city']) or $isadmin) && $user->city) {
     429          $userdetails['city'] = $user->city;
     430      }
     431  
     432      if (in_array('suspended', $userfields) && (!isset($hiddenfields['suspended']) or $isadmin)) {
     433          $userdetails['suspended'] = (bool)$user->suspended;
     434      }
     435  
     436      if (in_array('firstaccess', $userfields) && (!isset($hiddenfields['firstaccess']) or $isadmin)) {
     437          if ($user->firstaccess) {
     438              $userdetails['firstaccess'] = $user->firstaccess;
     439          } else {
     440              $userdetails['firstaccess'] = 0;
     441          }
     442      }
     443      if (in_array('lastaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
     444          if ($user->lastaccess) {
     445              $userdetails['lastaccess'] = $user->lastaccess;
     446          } else {
     447              $userdetails['lastaccess'] = 0;
     448          }
     449      }
     450  
     451      // Hidden fields restriction to lastaccess field applies to both site and course access time.
     452      if (in_array('lastcourseaccess', $userfields) && (!isset($hiddenfields['lastaccess']) or $isadmin)) {
     453          if (isset($user->lastcourseaccess)) {
     454              $userdetails['lastcourseaccess'] = $user->lastcourseaccess;
     455          } else {
     456              $userdetails['lastcourseaccess'] = 0;
     457          }
     458      }
     459  
     460      if (in_array('email', $userfields) && (
     461              $currentuser
     462              or (!isset($hiddenfields['email']) and (
     463                  $user->maildisplay == core_user::MAILDISPLAY_EVERYONE
     464                  or ($user->maildisplay == core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY and enrol_sharing_course($user, $USER))
     465                  or $canviewuseremail  // TODO: Deprecate/remove for MDL-37479.
     466              ))
     467              or in_array('email', $showuseridentityfields)
     468         )) {
     469          $userdetails['email'] = $user->email;
     470      }
     471  
     472      if (in_array('interests', $userfields)) {
     473          $interests = core_tag_tag::get_item_tags_array('core', 'user', $user->id, core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
     474          if ($interests) {
     475              $userdetails['interests'] = join(', ', $interests);
     476          }
     477      }
     478  
     479      // Departement/Institution/Idnumber are not displayed on any profile, however you can get them from editing profile.
     480      if (in_array('idnumber', $userfields) && $user->idnumber) {
     481          if (in_array('idnumber', $showuseridentityfields) or $currentuser or
     482                  has_capability('moodle/user:viewalldetails', $context)) {
     483              $userdetails['idnumber'] = $user->idnumber;
     484          }
     485      }
     486      if (in_array('institution', $userfields) && $user->institution) {
     487          if (in_array('institution', $showuseridentityfields) or $currentuser or
     488                  has_capability('moodle/user:viewalldetails', $context)) {
     489              $userdetails['institution'] = $user->institution;
     490          }
     491      }
     492      // Isset because it's ok to have department 0.
     493      if (in_array('department', $userfields) && isset($user->department)) {
     494          if (in_array('department', $showuseridentityfields) or $currentuser or
     495                  has_capability('moodle/user:viewalldetails', $context)) {
     496              $userdetails['department'] = $user->department;
     497          }
     498      }
     499  
     500      if (in_array('roles', $userfields) && !empty($course)) {
     501          // Not a big secret.
     502          $roles = get_user_roles($context, $user->id, false);
     503          $userdetails['roles'] = array();
     504          foreach ($roles as $role) {
     505              $userdetails['roles'][] = array(
     506                  'roleid'       => $role->roleid,
     507                  'name'         => $role->name,
     508                  'shortname'    => $role->shortname,
     509                  'sortorder'    => $role->sortorder
     510              );
     511          }
     512      }
     513  
     514      // If groups are in use and enforced throughout the course, then make sure we can meet in at least one course level group.
     515      if (in_array('groups', $userfields) && !empty($course) && $canaccessallgroups) {
     516          $usergroups = groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid,
     517                  'g.id, g.name,g.description,g.descriptionformat');
     518          $userdetails['groups'] = array();
     519          foreach ($usergroups as $group) {
     520              list($group->description, $group->descriptionformat) =
     521                  external_format_text($group->description, $group->descriptionformat,
     522                          $context->id, 'group', 'description', $group->id);
     523              $userdetails['groups'][] = array('id' => $group->id, 'name' => $group->name,
     524                  'description' => $group->description, 'descriptionformat' => $group->descriptionformat);
     525          }
     526      }
     527      // List of courses where the user is enrolled.
     528      if (in_array('enrolledcourses', $userfields) && !isset($hiddenfields['mycourses'])) {
     529          $enrolledcourses = array();
     530          if ($mycourses = enrol_get_users_courses($user->id, true)) {
     531              foreach ($mycourses as $mycourse) {
     532                  if ($mycourse->category) {
     533                      $coursecontext = context_course::instance($mycourse->id);
     534                      $enrolledcourse = array();
     535                      $enrolledcourse['id'] = $mycourse->id;
     536                      $enrolledcourse['fullname'] = format_string($mycourse->fullname, true, array('context' => $coursecontext));
     537                      $enrolledcourse['shortname'] = format_string($mycourse->shortname, true, array('context' => $coursecontext));
     538                      $enrolledcourses[] = $enrolledcourse;
     539                  }
     540              }
     541              $userdetails['enrolledcourses'] = $enrolledcourses;
     542          }
     543      }
     544  
     545      // User preferences.
     546      if (in_array('preferences', $userfields) && $currentuser) {
     547          $preferences = array();
     548          $userpreferences = get_user_preferences();
     549          foreach ($userpreferences as $prefname => $prefvalue) {
     550              $preferences[] = array('name' => $prefname, 'value' => $prefvalue);
     551          }
     552          $userdetails['preferences'] = $preferences;
     553      }
     554  
     555      if ($currentuser or has_capability('moodle/user:viewalldetails', $context)) {
     556          $extrafields = ['auth', 'confirmed', 'lang', 'theme', 'timezone', 'mailformat'];
     557          foreach ($extrafields as $extrafield) {
     558              if (in_array($extrafield, $userfields) && isset($user->$extrafield)) {
     559                  $userdetails[$extrafield] = $user->$extrafield;
     560              }
     561          }
     562      }
     563  
     564      // Clean lang and auth fields for external functions (it may content uninstalled themes or language packs).
     565      if (isset($userdetails['lang'])) {
     566          $userdetails['lang'] = clean_param($userdetails['lang'], PARAM_LANG);
     567      }
     568      if (isset($userdetails['theme'])) {
     569          $userdetails['theme'] = clean_param($userdetails['theme'], PARAM_THEME);
     570      }
     571  
     572      return $userdetails;
     573  }
     574  
     575  /**
     576   * Tries to obtain user details, either recurring directly to the user's system profile
     577   * or through one of the user's course enrollments (course profile).
     578   *
     579   * @param stdClass $user The user.
     580   * @return array if unsuccessful or the allowed user details.
     581   */
     582  function user_get_user_details_courses($user) {
     583      global $USER;
     584      $userdetails = null;
     585  
     586      $systemprofile = false;
     587      if (can_view_user_details_cap($user) || ($user->id == $USER->id) || has_coursecontact_role($user->id)) {
     588          $systemprofile = true;
     589      }
     590  
     591      // Try using system profile.
     592      if ($systemprofile) {
     593          $userdetails = user_get_user_details($user, null);
     594      } else {
     595          // Try through course profile.
     596          // Get the courses that the user is enrolled in (only active).
     597          $courses = enrol_get_users_courses($user->id, true);
     598          foreach ($courses as $course) {
     599              if (user_can_view_profile($user, $course)) {
     600                  $userdetails = user_get_user_details($user, $course);
     601              }
     602          }
     603      }
     604  
     605      return $userdetails;
     606  }
     607  
     608  /**
     609   * Check if $USER have the necessary capabilities to obtain user details.
     610   *
     611   * @param stdClass $user
     612   * @param stdClass $course if null then only consider system profile otherwise also consider the course's profile.
     613   * @return bool true if $USER can view user details.
     614   */
     615  function can_view_user_details_cap($user, $course = null) {
     616      // Check $USER has the capability to view the user details at user context.
     617      $usercontext = context_user::instance($user->id);
     618      $result = has_capability('moodle/user:viewdetails', $usercontext);
     619      // Otherwise can $USER see them at course context.
     620      if (!$result && !empty($course)) {
     621          $context = context_course::instance($course->id);
     622          $result = has_capability('moodle/user:viewdetails', $context);
     623      }
     624      return $result;
     625  }
     626  
     627  /**
     628   * Return a list of page types
     629   * @param string $pagetype current page type
     630   * @param stdClass $parentcontext Block's parent context
     631   * @param stdClass $currentcontext Current context of block
     632   * @return array
     633   */
     634  function user_page_type_list($pagetype, $parentcontext, $currentcontext) {
     635      return array('user-profile' => get_string('page-user-profile', 'pagetype'));
     636  }
     637  
     638  /**
     639   * Count the number of failed login attempts for the given user, since last successful login.
     640   *
     641   * @param int|stdclass $user user id or object.
     642   * @param bool $reset Resets failed login count, if set to true.
     643   *
     644   * @return int number of failed login attempts since the last successful login.
     645   */
     646  function user_count_login_failures($user, $reset = true) {
     647      global $DB;
     648  
     649      if (!is_object($user)) {
     650          $user = $DB->get_record('user', array('id' => $user), '*', MUST_EXIST);
     651      }
     652      if ($user->deleted) {
     653          // Deleted user, nothing to do.
     654          return 0;
     655      }
     656      $count = get_user_preferences('login_failed_count_since_success', 0, $user);
     657      if ($reset) {
     658          set_user_preference('login_failed_count_since_success', 0, $user);
     659      }
     660      return $count;
     661  }
     662  
     663  /**
     664   * Converts a string into a flat array of menu items, where each menu items is a
     665   * stdClass with fields type, url, title, pix, and imgsrc.
     666   *
     667   * @param string $text the menu items definition
     668   * @param moodle_page $page the current page
     669   * @return array
     670   */
     671  function user_convert_text_to_menu_items($text, $page) {
     672      global $OUTPUT, $CFG;
     673  
     674      $lines = explode("\n", $text);
     675      $items = array();
     676      $lastchild = null;
     677      $lastdepth = null;
     678      $lastsort = 0;
     679      $children = array();
     680      foreach ($lines as $line) {
     681          $line = trim($line);
     682          $bits = explode('|', $line, 3);
     683          $itemtype = 'link';
     684          if (preg_match("/^#+$/", $line)) {
     685              $itemtype = 'divider';
     686          } else if (!array_key_exists(0, $bits) or empty($bits[0])) {
     687              // Every item must have a name to be valid.
     688              continue;
     689          } else {
     690              $bits[0] = ltrim($bits[0], '-');
     691          }
     692  
     693          // Create the child.
     694          $child = new stdClass();
     695          $child->itemtype = $itemtype;
     696          if ($itemtype === 'divider') {
     697              // Add the divider to the list of children and skip link
     698              // processing.
     699              $children[] = $child;
     700              continue;
     701          }
     702  
     703          // Name processing.
     704          $namebits = explode(',', $bits[0], 2);
     705          if (count($namebits) == 2) {
     706              // Check the validity of the identifier part of the string.
     707              if (clean_param($namebits[0], PARAM_STRINGID) !== '') {
     708                  // Treat this as a language string.
     709                  $child->title = get_string($namebits[0], $namebits[1]);
     710                  $child->titleidentifier = implode(',', $namebits);
     711              }
     712          }
     713          if (empty($child->title)) {
     714              // Use it as is, don't even clean it.
     715              $child->title = $bits[0];
     716              $child->titleidentifier = str_replace(" ", "-", $bits[0]);
     717          }
     718  
     719          // URL processing.
     720          if (!array_key_exists(1, $bits) or empty($bits[1])) {
     721              // Set the url to null, and set the itemtype to invalid.
     722              $bits[1] = null;
     723              $child->itemtype = "invalid";
     724          } else {
     725              // Nasty hack to replace the grades with the direct url.
     726              if (strpos($bits[1], '/grade/report/mygrades.php') !== false) {
     727                  $bits[1] = user_mygrades_url();
     728              }
     729  
     730              // Make sure the url is a moodle url.
     731              $bits[1] = new moodle_url(trim($bits[1]));
     732          }
     733          $child->url = $bits[1];
     734  
     735          // PIX processing.
     736          $pixpath = "t/edit";
     737          if (!array_key_exists(2, $bits) or empty($bits[2])) {
     738              // Use the default.
     739              $child->pix = $pixpath;
     740          } else {
     741              // Check for the specified image existing.
     742              if (strpos($bits[2], '../') === 0) {
     743                  // The string starts with '../'.
     744                  // Strip off the first three characters - this should be the pix path.
     745                  $pixpath = substr($bits[2], 3);
     746              } else if (strpos($bits[2], '/') === false) {
     747                  // There is no / in the path. Prefix it with 't/', which is the default path.
     748                  $pixpath = "t/{$bits[2]}";
     749              } else {
     750                  // There is a '/' in the path - this is either a URL, or a standard pix path with no changes required.
     751                  $pixpath = $bits[2];
     752              }
     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  
     769  /**
     770   * Get a list of essential user navigation items.
     771   *
     772   * @param stdclass $user user object.
     773   * @param moodle_page $page page object.
     774   * @param array $options associative array.
     775   *     options are:
     776   *     - avatarsize=35 (size of avatar image)
     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   */
     819  function user_get_user_navigation_info($user, $page, $options = array()) {
     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);
     834      $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
     835          'id' => $user->id
     836      ));
     837  
     838      $avataroptions = array('link' => false, 'visibletoscreenreaders' => false);
     839      if (!empty($options['avatarsize'])) {
     840          $avataroptions['size'] = $options['avatarsize'];
     841      }
     842      $returnobject->metadata['useravatar'] = $OUTPUT->user_picture (
     843          $user, $avataroptions
     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)) {
     858              // Don't reset the count either, as login_info() still needs it too.
     859              if ($count = user_count_login_failures($user, false)) {
     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  
     871      // Links: Dashboard.
     872      $myhome = new stdClass();
     873      $myhome->itemtype = 'link';
     874      $myhome->url = new moodle_url('/my/');
     875      $myhome->title = get_string('mymoodle', 'admin');
     876      $myhome->titleidentifier = 'mymoodle,admin';
     877      $myhome->pix = "i/dashboard";
     878      $returnobject->navitems[] = $myhome;
     879  
     880      // Links: My Profile.
     881      $myprofile = new stdClass();
     882      $myprofile->itemtype = 'link';
     883      $myprofile->url = new moodle_url('/user/profile.php', array('id' => $user->id));
     884      $myprofile->title = get_string('profile');
     885      $myprofile->titleidentifier = 'profile,moodle';
     886      $myprofile->pix = "i/user";
     887      $returnobject->navitems[] = $myprofile;
     888  
     889      $returnobject->metadata['asotherrole'] = false;
     890  
     891      // Before we add the last items (usually a logout + switch role link), add any
     892      // custom-defined items.
     893      $customitems = user_convert_text_to_menu_items($CFG->customusermenuitems, $page);
     894      foreach ($customitems as $item) {
     895          $returnobject->navitems[] = $item;
     896      }
     897  
     898  
     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);
     906          $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
     907              'id' => $realuser->id
     908          ));
     909          $returnobject->metadata['realuseravatar'] = $OUTPUT->user_picture($realuser, $avataroptions);
     910  
     911          // Build a user-revert link.
     912          $userrevert = new stdClass();
     913          $userrevert->itemtype = 'link';
     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');
     920          $userrevert->titleidentifier = 'logout,moodle';
     921          $returnobject->navitems[] = $userrevert;
     922  
     923      } else {
     924  
     925          // Build a logout link.
     926          $logout = new stdClass();
     927          $logout->itemtype = 'link';
     928          $logout->url = new moodle_url('/login/logout.php', array('sesskey' => sesskey()));
     929          $logout->pix = "a/logout";
     930          $logout->title = get_string('logout');
     931          $logout->titleidentifier = 'logout,moodle';
     932          $returnobject->navitems[] = $logout;
     933      }
     934  
     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);
     953  
     954          }
     955      } else {
     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          }
     971      }
     972  
     973      return $returnobject;
     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   */
     988  function user_add_password_history($userid, $password) {
     989      global $CFG, $DB;
     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   */
    1026  function user_is_previously_used_password($userid, $password) {
    1027      global $CFG, $DB;
    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  }
    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   */
    1060  function 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  }
    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   */
    1084  function 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  }
    1097  
    1098  /**
    1099   * Returns the url to use for the "Grades" link in the user navigation.
    1100   *
    1101   * @param int $userid The user's ID.
    1102   * @param int $courseid The course ID if available.
    1103   * @return mixed A URL to be directed to for "Grades".
    1104   */
    1105  function 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  }
    1124  
    1125  /**
    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.
    1132   *
    1133   * @param object $user The other user's details.
    1134   * @param object $course if provided, only check permissions in this course.
    1135   * @param context $usercontext The user context if available.
    1136   * @return bool true for ability to view this user, else false.
    1137   */
    1138  function user_can_view_profile($user, $course = null, $usercontext = null) {
    1139      global $USER, $CFG;
    1140  
    1141      if ($user->deleted) {
    1142          return false;
    1143      }
    1144  
    1145      // Do we need to be logged in?
    1146      if (empty($CFG->forceloginforprofiles)) {
    1147          return true;
    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          }
    1153      }
    1154  
    1155      // Current user can always view their profile.
    1156      if ($USER->id == $user->id) {
    1157          return true;
    1158      }
    1159  
    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  
    1187      // Course contacts have visible profiles always.
    1188      if (has_coursecontact_role($user->id)) {
    1189          return true;
    1190      }
    1191  
    1192      // If we're only checking the capabilities in the single provided course.
    1193      if (isset($course)) {
    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          }
    1198      } else {
    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          }
    1206          // This returns context information, so we can preload below.
    1207          $userscourses = enrol_get_all_users_courses($user->id);
    1208      }
    1209  
    1210      if (empty($userscourses)) {
    1211          return false;
    1212      }
    1213  
    1214      foreach ($userscourses as $userscourse) {
    1215          context_helper::preload_from_record($userscourse);
    1216          $coursecontext = context_course::instance($userscourse->id);
    1217          if (has_capability('moodle/user:viewdetails', $coursecontext) ||
    1218              has_capability('moodle/user:viewalldetails', $coursecontext)) {
    1219              if (!groups_user_groups_visible($userscourse, $user->id)) {
    1220                  // Not a member of the same group.
    1221                  continue;
    1222              }
    1223              return true;
    1224          }
    1225      }
    1226      return false;
    1227  }
    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   */
    1242  function 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  }
    1266  
    1267  /**
    1268   * Returns SQL that can be used to limit a query to a period where the user last accessed / did not access a course.
    1269   *
    1270   * @param int $accesssince The unix timestamp to compare to users' last access
    1271   * @param string $tableprefix
    1272   * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
    1273   * @return string
    1274   */
    1275  function user_get_course_lastaccess_sql($accesssince = null, $tableprefix = 'ul', $haveaccessed = false) {
    1276      return user_get_lastaccess_sql('timeaccess', $accesssince, $tableprefix, $haveaccessed);
    1277  }
    1278  
    1279  /**
    1280   * Returns SQL that can be used to limit a query to a period where the user last accessed / did not access the system.
    1281   *
    1282   * @param int $accesssince The unix timestamp to compare to users' last access
    1283   * @param string $tableprefix
    1284   * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
    1285   * @return string
    1286   */
    1287  function user_get_user_lastaccess_sql($accesssince = null, $tableprefix = 'u', $haveaccessed = false) {
    1288      return user_get_lastaccess_sql('lastaccess', $accesssince, $tableprefix, $haveaccessed);
    1289  }
    1290  
    1291  /**
    1292   * Returns SQL that can be used to limit a query to a period where the user last accessed or
    1293   * did not access something recorded by a given table.
    1294   *
    1295   * @param string $columnname The name of the access column to check against
    1296   * @param int $accesssince The unix timestamp to compare to users' last access
    1297   * @param string $tableprefix The query prefix of the table to check
    1298   * @param bool $haveaccessed Whether to match against users who HAVE accessed since $accesssince (optional)
    1299   * @return string
    1300   */
    1301  function user_get_lastaccess_sql($columnname, $accesssince, $tableprefix, $haveaccessed = false) {
    1302      if (empty($accesssince)) {
    1303          return '';
    1304      }
    1305  
    1306      // Only users who have accessed since $accesssince.
    1307      if ($haveaccessed) {
    1308          if ($accesssince == -1) {
    1309              // Include all users who have logged in at some point.
    1310              $sql = "({$tableprefix}.{$columnname} IS NOT NULL AND {$tableprefix}.{$columnname} != 0)";
    1311          } else {
    1312              // Users who have accessed since the specified time.
    1313              $sql = "{$tableprefix}.{$columnname} IS NOT NULL AND {$tableprefix}.{$columnname} != 0
    1314                  AND {$tableprefix}.{$columnname} >= {$accesssince}";
    1315          }
    1316      } else {
    1317          // Only users who have not accessed since $accesssince.
    1318  
    1319          if ($accesssince == -1) {
    1320              // Users who have never accessed.
    1321              $sql = "({$tableprefix}.{$columnname} IS NULL OR {$tableprefix}.{$columnname} = 0)";
    1322          } else {
    1323              // Users who have not accessed since the specified time.
    1324              $sql = "({$tableprefix}.{$columnname} IS NULL
    1325                      OR ({$tableprefix}.{$columnname} != 0 AND {$tableprefix}.{$columnname} < {$accesssince}))";
    1326          }
    1327      }
    1328  
    1329      return $sql;
    1330  }
    1331  
    1332  /**
    1333   * Callback for inplace editable API.
    1334   *
    1335   * @param string $itemtype - Only user_roles is supported.
    1336   * @param string $itemid - Courseid and userid separated by a :
    1337   * @param string $newvalue - json encoded list of roleids.
    1338   * @return \core\output\inplace_editable
    1339   */
    1340  function core_user_inplace_editable($itemtype, $itemid, $newvalue) {
    1341      if ($itemtype === 'user_roles') {
    1342          return \core_user\output\user_roles_editable::update($itemid, $newvalue);
    1343      }
    1344  }
    1345  
    1346  /**
    1347   * Map an internal field name to a valid purpose from: "https://www.w3.org/TR/WCAG21/#input-purposes"
    1348   *
    1349   * @param integer $userid
    1350   * @param string $fieldname
    1351   * @return string $purpose (empty string if there is no mapping).
    1352   */
    1353  function user_edit_map_field_purpose($userid, $fieldname) {
    1354      global $USER;
    1355  
    1356      $currentuser = ($userid == $USER->id) && !\core\session\manager::is_loggedinas();
    1357      // These are the fields considered valid to map and auto fill from a browser.
    1358      // We do not include fields that are in a collapsed section by default because
    1359      // the browser could auto-fill the field and cause a new value to be saved when
    1360      // that field was never visible.
    1361      $validmappings = array(
    1362          'username' => 'username',
    1363          'password' => 'current-password',
    1364          'firstname' => 'given-name',
    1365          'lastname' => 'family-name',
    1366          'middlename' => 'additional-name',
    1367          'email' => 'email',
    1368          'country' => 'country',
    1369          'lang' => 'language'
    1370      );
    1371  
    1372      $purpose = '';
    1373      // Only set a purpose when editing your own user details.
    1374      if ($currentuser && isset($validmappings[$fieldname])) {
    1375          $purpose = ' autocomplete="' . $validmappings[$fieldname] . '" ';
    1376      }
    1377  
    1378      return $purpose;
    1379  }
    1380