Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 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   * User class
  19   *
  20   * @package    core
  21   * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /**
  28   * User class to access user details.
  29   *
  30   * @todo       move api's from user/lib.php and deprecate old ones.
  31   * @package    core
  32   * @copyright  2013 Rajesh Taneja <rajesh@moodle.com>
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class core_user {
  36      /**
  37       * No reply user id.
  38       */
  39      const NOREPLY_USER = -10;
  40  
  41      /**
  42       * Support user id.
  43       */
  44      const SUPPORT_USER = -20;
  45  
  46      /**
  47       * Hide email address from everyone.
  48       */
  49      const MAILDISPLAY_HIDE = 0;
  50  
  51      /**
  52       * Display email address to everyone.
  53       */
  54      const MAILDISPLAY_EVERYONE = 1;
  55  
  56      /**
  57       * Display email address to course members only.
  58       */
  59      const MAILDISPLAY_COURSE_MEMBERS_ONLY = 2;
  60  
  61      /**
  62       * List of fields that can be synched/locked during authentication.
  63       */
  64      const AUTHSYNCFIELDS = [
  65          'firstname',
  66          'lastname',
  67          'email',
  68          'city',
  69          'country',
  70          'lang',
  71          'description',
  72          'idnumber',
  73          'institution',
  74          'department',
  75          'phone1',
  76          'phone2',
  77          'address',
  78          'firstnamephonetic',
  79          'lastnamephonetic',
  80          'middlename',
  81          'alternatename'
  82      ];
  83  
  84      /** @var int Indicates that user profile view should be prevented */
  85      const VIEWPROFILE_PREVENT = -1;
  86      /** @var int Indicates that user profile view should not be prevented */
  87      const VIEWPROFILE_DO_NOT_PREVENT = 0;
  88      /** @var int Indicates that user profile view should be allowed even if Moodle would prevent it */
  89      const VIEWPROFILE_FORCE_ALLOW = 1;
  90  
  91      /** @var stdClass keep record of noreply user */
  92      public static $noreplyuser = false;
  93  
  94      /** @var stdClass keep record of support user */
  95      public static $supportuser = false;
  96  
  97      /** @var array store user fields properties cache. */
  98      protected static $propertiescache = null;
  99  
 100      /** @var array store user preferences cache. */
 101      protected static $preferencescache = null;
 102  
 103      /**
 104       * Return user object from db or create noreply or support user,
 105       * if userid matches corse_user::NOREPLY_USER or corse_user::SUPPORT_USER
 106       * respectively. If userid is not found, then return false.
 107       *
 108       * @param int $userid user id
 109       * @param string $fields A comma separated list of user fields to be returned, support and noreply user
 110       *                       will not be filtered by this.
 111       * @param int $strictness IGNORE_MISSING means compatible mode, false returned if user not found, debug message if more found;
 112       *                        IGNORE_MULTIPLE means return first user, ignore multiple user records found(not recommended);
 113       *                        MUST_EXIST means throw an exception if no user record or multiple records found.
 114       * @return stdClass|bool user record if found, else false.
 115       * @throws dml_exception if user record not found and respective $strictness is set.
 116       */
 117      public static function get_user($userid, $fields = '*', $strictness = IGNORE_MISSING) {
 118          global $DB;
 119  
 120          // If noreply user then create fake record and return.
 121          switch ($userid) {
 122              case self::NOREPLY_USER:
 123                  return self::get_noreply_user();
 124                  break;
 125              case self::SUPPORT_USER:
 126                  return self::get_support_user();
 127                  break;
 128              default:
 129                  return $DB->get_record('user', array('id' => $userid), $fields, $strictness);
 130          }
 131      }
 132  
 133      /**
 134       * Return user object from db based on their email.
 135       *
 136       * @param string $email The email of the user searched.
 137       * @param string $fields A comma separated list of user fields to be returned, support and noreply user.
 138       * @param int $mnethostid The id of the remote host.
 139       * @param int $strictness IGNORE_MISSING means compatible mode, false returned if user not found, debug message if more found;
 140       *                        IGNORE_MULTIPLE means return first user, ignore multiple user records found(not recommended);
 141       *                        MUST_EXIST means throw an exception if no user record or multiple records found.
 142       * @return stdClass|bool user record if found, else false.
 143       * @throws dml_exception if user record not found and respective $strictness is set.
 144       */
 145      public static function get_user_by_email($email, $fields = '*', $mnethostid = null, $strictness = IGNORE_MISSING) {
 146          global $DB, $CFG;
 147  
 148          // Because we use the username as the search criteria, we must also restrict our search based on mnet host.
 149          if (empty($mnethostid)) {
 150              // If empty, we restrict to local users.
 151              $mnethostid = $CFG->mnet_localhost_id;
 152          }
 153  
 154          return $DB->get_record('user', array('email' => $email, 'mnethostid' => $mnethostid), $fields, $strictness);
 155      }
 156  
 157      /**
 158       * Return user object from db based on their username.
 159       *
 160       * @param string $username The username of the user searched.
 161       * @param string $fields A comma separated list of user fields to be returned, support and noreply user.
 162       * @param int $mnethostid The id of the remote host.
 163       * @param int $strictness IGNORE_MISSING means compatible mode, false returned if user not found, debug message if more found;
 164       *                        IGNORE_MULTIPLE means return first user, ignore multiple user records found(not recommended);
 165       *                        MUST_EXIST means throw an exception if no user record or multiple records found.
 166       * @return stdClass|bool user record if found, else false.
 167       * @throws dml_exception if user record not found and respective $strictness is set.
 168       */
 169      public static function get_user_by_username($username, $fields = '*', $mnethostid = null, $strictness = IGNORE_MISSING) {
 170          global $DB, $CFG;
 171  
 172          // Because we use the username as the search criteria, we must also restrict our search based on mnet host.
 173          if (empty($mnethostid)) {
 174              // If empty, we restrict to local users.
 175              $mnethostid = $CFG->mnet_localhost_id;
 176          }
 177  
 178          return $DB->get_record('user', array('username' => $username, 'mnethostid' => $mnethostid), $fields, $strictness);
 179      }
 180  
 181      /**
 182       * Searches for users by name, possibly within a specified context, with current user's access.
 183       *
 184       * Deciding which users to search is complicated because it relies on user permissions;
 185       * ideally, we shouldn't show names if you aren't allowed to see their profile. The permissions
 186       * for seeing profile are really complicated.
 187       *
 188       * Even if search is restricted to a course, it's possible that other people might have
 189       * been able to contribute within the course (e.g. they were enrolled before and not now;
 190       * or people with system-level roles) so if the user has permission we do want to include
 191       * everyone. However, if there are multiple results then we prioritise the ones who are
 192       * enrolled in the course.
 193       *
 194       * If you have moodle/user:viewdetails at system level, you can search everyone.
 195       * Otherwise we check which courses you *do* have that permission and search everyone who is
 196       * enrolled on those courses.
 197       *
 198       * Normally you can only search the user's name. If you have the moodle/site:viewuseridentity
 199       * capability then we also let you search the fields which are listed as identity fields in
 200       * the 'showuseridentity' config option. For example, this might include the user's ID number
 201       * or email.
 202       *
 203       * The $max parameter controls the maximum number of users returned. If users are restricted
 204       * from view for some reason, multiple runs of the main query might be made; the $querylimit
 205       * parameter allows this to be restricted. Both parameters can be zero to remove limits.
 206       *
 207       * The returned user objects include id, username, all fields required for user pictures, and
 208       * user identity fields.
 209       *
 210       * @param string $query Search query text
 211       * @param \context_course|null $coursecontext Course context or null if system-wide
 212       * @param int $max Max number of users to return, default 30 (zero = no limit)
 213       * @param int $querylimit Max number of database queries, default 5 (zero = no limit)
 214       * @return array Array of user objects with limited fields
 215       */
 216      public static function search($query, \context_course $coursecontext = null,
 217              $max = 30, $querylimit = 5) {
 218          global $CFG, $DB;
 219          require_once($CFG->dirroot . '/user/lib.php');
 220  
 221          // Allow limits to be turned off.
 222          if (!$max) {
 223              $max = PHP_INT_MAX;
 224          }
 225          if (!$querylimit) {
 226              $querylimit = PHP_INT_MAX;
 227          }
 228  
 229          // Check permission to view profiles at each context.
 230          $systemcontext = \context_system::instance();
 231          $viewsystem = has_capability('moodle/user:viewdetails', $systemcontext);
 232          if ($viewsystem) {
 233              $userquery = 'SELECT id FROM {user}';
 234              $userparams = [];
 235          }
 236          if (!$viewsystem) {
 237              list($userquery, $userparams) = self::get_enrolled_sql_on_courses_with_capability(
 238                      'moodle/user:viewdetails');
 239              if (!$userquery) {
 240                  // No permissions anywhere, return nothing.
 241                  return [];
 242              }
 243          }
 244  
 245          // Start building the WHERE clause based on name.
 246          list ($where, $whereparams) = users_search_sql($query, 'u', false);
 247  
 248          // We allow users to search with extra identity fields (as well as name) but only if they
 249          // have the permission to display those identity fields.
 250          $extrasql = '';
 251          $extraparams = [];
 252  
 253          // TODO Does not support custom user profile fields (MDL-70456).
 254          $userfieldsapi = \core_user\fields::for_identity(null, false)->with_userpic()->with_name()
 255              ->including('username', 'deleted');
 256          $selectfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
 257          $extra = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
 258  
 259          $index = 1;
 260          foreach ($extra as $fieldname) {
 261              if ($extrasql) {
 262                  $extrasql .= ' OR ';
 263              }
 264              $extrasql .= $DB->sql_like('u.' . $fieldname, ':extra' . $index, false);
 265              $extraparams['extra' . $index] = $query . '%';
 266              $index++;
 267          }
 268  
 269          $identitysystem = has_capability('moodle/site:viewuseridentity', $systemcontext);
 270          $usingshowidentity = false;
 271          if ($identitysystem) {
 272              // They have permission everywhere so just add the extra query to the normal query.
 273              $where .= ' OR ' . $extrasql;
 274              $whereparams = array_merge($whereparams, $extraparams);
 275          } else {
 276              // Get all courses where user can view full user identity.
 277              list($sql, $params) = self::get_enrolled_sql_on_courses_with_capability(
 278                      'moodle/site:viewuseridentity');
 279              if ($sql) {
 280                  // Join that with the user query to get an extra field indicating if we can.
 281                  $userquery = "
 282                          SELECT innerusers.id, COUNT(identityusers.id) AS showidentity
 283                            FROM ($userquery) innerusers
 284                       LEFT JOIN ($sql) identityusers ON identityusers.id = innerusers.id
 285                        GROUP BY innerusers.id";
 286                  $userparams = array_merge($userparams, $params);
 287                  $usingshowidentity = true;
 288  
 289                  // Query on the extra fields only in those places.
 290                  $where .= ' OR (users.showidentity > 0 AND (' . $extrasql . '))';
 291                  $whereparams = array_merge($whereparams, $extraparams);
 292              }
 293          }
 294  
 295          // Default order is just name order. But if searching within a course then we show users
 296          // within the course first.
 297          list ($order, $orderparams) = users_order_by_sql('u', $query, $systemcontext);
 298          if ($coursecontext) {
 299              list ($sql, $params) = get_enrolled_sql($coursecontext);
 300              $mainfield = 'innerusers2.id';
 301              if ($usingshowidentity) {
 302                  $mainfield .= ', innerusers2.showidentity';
 303              }
 304              $userquery = "
 305                      SELECT $mainfield, COUNT(courseusers.id) AS incourse
 306                        FROM ($userquery) innerusers2
 307                   LEFT JOIN ($sql) courseusers ON courseusers.id = innerusers2.id
 308                    GROUP BY $mainfield";
 309              $userparams = array_merge($userparams, $params);
 310  
 311              $order = 'incourse DESC, ' . $order;
 312          }
 313  
 314          // Get result (first 30 rows only) from database. Take a couple spare in case we have to
 315          // drop some.
 316          $result = [];
 317          $got = 0;
 318          $pos = 0;
 319          $readcount = $max + 2;
 320          for ($i = 0; $i < $querylimit; $i++) {
 321              $rawresult = $DB->get_records_sql("
 322                      SELECT $selectfields
 323                        FROM ($userquery) users
 324                        JOIN {user} u ON u.id = users.id
 325                       WHERE $where
 326                    ORDER BY $order", array_merge($userparams, $whereparams, $orderparams),
 327                      $pos, $readcount);
 328              foreach ($rawresult as $user) {
 329                  // Skip guest.
 330                  if ($user->username === 'guest') {
 331                      continue;
 332                  }
 333                  // Check user can really view profile (there are per-user cases where this could
 334                  // be different for some reason, this is the same check used by the profile view pages
 335                  // to double-check that it is OK).
 336                  if (!user_can_view_profile($user)) {
 337                      continue;
 338                  }
 339                  $result[] = $user;
 340                  $got++;
 341                  if ($got >= $max) {
 342                      break;
 343                  }
 344              }
 345  
 346              if ($got >= $max) {
 347                  // All necessary results obtained.
 348                  break;
 349              }
 350              if (count($rawresult) < $readcount) {
 351                  // No more results from database.
 352                  break;
 353              }
 354              $pos += $readcount;
 355          }
 356  
 357          return $result;
 358      }
 359  
 360      /**
 361       * Gets an SQL query that lists all enrolled user ids on any course where the current
 362       * user has the specified capability. Helper function used for searching users.
 363       *
 364       * @param string $capability Required capability
 365       * @return array Array containing SQL and params, or two nulls if there are no courses
 366       */
 367      protected static function get_enrolled_sql_on_courses_with_capability($capability) {
 368          // Get all courses where user have the capability.
 369          $courses = get_user_capability_course($capability, null, true,
 370                  implode(',', array_values(context_helper::get_preload_record_columns('ctx'))));
 371          if (!$courses) {
 372              return [null, null];
 373          }
 374  
 375          // Loop around all courses getting the SQL for enrolled users. Note: This query could
 376          // probably be more efficient (without the union) if get_enrolled_sql had a way to
 377          // pass an array of courseids, but it doesn't.
 378          $unionsql = '';
 379          $unionparams = [];
 380          foreach ($courses as $course) {
 381              // Get SQL to list user ids enrolled in this course.
 382              \context_helper::preload_from_record($course);
 383              list ($sql, $params) = get_enrolled_sql(\context_course::instance($course->id));
 384  
 385              // Combine to a big union query.
 386              if ($unionsql) {
 387                  $unionsql .= ' UNION ';
 388              }
 389              $unionsql .= $sql;
 390              $unionparams = array_merge($unionparams, $params);
 391          }
 392  
 393          return [$unionsql, $unionparams];
 394      }
 395  
 396      /**
 397       * Helper function to return dummy noreply user record.
 398       *
 399       * @return stdClass
 400       */
 401      protected static function get_dummy_user_record() {
 402          global $CFG;
 403  
 404          $dummyuser = new stdClass();
 405          $dummyuser->id = self::NOREPLY_USER;
 406          $dummyuser->email = $CFG->noreplyaddress;
 407          $dummyuser->firstname = get_string('noreplyname');
 408          $dummyuser->username = 'noreply';
 409          $dummyuser->lastname = '';
 410          $dummyuser->confirmed = 1;
 411          $dummyuser->suspended = 0;
 412          $dummyuser->deleted = 0;
 413          $dummyuser->picture = 0;
 414          $dummyuser->auth = 'manual';
 415          $dummyuser->firstnamephonetic = '';
 416          $dummyuser->lastnamephonetic = '';
 417          $dummyuser->middlename = '';
 418          $dummyuser->alternatename = '';
 419          $dummyuser->imagealt = '';
 420          return $dummyuser;
 421      }
 422  
 423      /**
 424       * Return noreply user record, this is currently used in messaging
 425       * system only for sending messages from noreply email.
 426       * It will return record of $CFG->noreplyuserid if set else return dummy
 427       * user object with hard-coded $user->emailstop = 1 so noreply can be sent to user.
 428       *
 429       * @return stdClass user record.
 430       */
 431      public static function get_noreply_user() {
 432          global $CFG;
 433  
 434          if (!empty(self::$noreplyuser)) {
 435              return self::$noreplyuser;
 436          }
 437  
 438          // If noreply user is set then use it, else create one.
 439          if (!empty($CFG->noreplyuserid)) {
 440              self::$noreplyuser = self::get_user($CFG->noreplyuserid);
 441              self::$noreplyuser->emailstop = 1; // Force msg stop for this user.
 442              return self::$noreplyuser;
 443          } else {
 444              // Do not cache the dummy user record to avoid language internationalization issues.
 445              $noreplyuser = self::get_dummy_user_record();
 446              $noreplyuser->maildisplay = '1'; // Show to all.
 447              $noreplyuser->emailstop = 1;
 448              return $noreplyuser;
 449          }
 450      }
 451  
 452      /**
 453       * Return support user record, this is currently used in messaging
 454       * system only for sending messages to support email.
 455       * $CFG->supportuserid is set then returns user record
 456       * $CFG->supportemail is set then return dummy record with $CFG->supportemail
 457       * else return admin user record with hard-coded $user->emailstop = 0, so user
 458       * gets support message.
 459       *
 460       * @return stdClass user record.
 461       */
 462      public static function get_support_user() {
 463          global $CFG;
 464  
 465          if (!empty(self::$supportuser)) {
 466              return self::$supportuser;
 467          }
 468  
 469          // If custom support user is set then use it, else if supportemail is set then use it, else use noreply.
 470          if (!empty($CFG->supportuserid)) {
 471              self::$supportuser = self::get_user($CFG->supportuserid, '*', MUST_EXIST);
 472          } else if (empty(self::$supportuser) && !empty($CFG->supportemail)) {
 473              // Try sending it to support email if support user is not set.
 474              $supportuser = self::get_dummy_user_record();
 475              $supportuser->id = self::SUPPORT_USER;
 476              $supportuser->email = $CFG->supportemail;
 477              if ($CFG->supportname) {
 478                  $supportuser->firstname = $CFG->supportname;
 479              }
 480              $supportuser->username = 'support';
 481              $supportuser->maildisplay = '1'; // Show to all.
 482              // Unset emailstop to make sure support message is sent.
 483              $supportuser->emailstop = 0;
 484              return $supportuser;
 485          }
 486  
 487          // Send support msg to admin user if nothing is set above.
 488          if (empty(self::$supportuser)) {
 489              self::$supportuser = get_admin();
 490          }
 491  
 492          // Unset emailstop to make sure support message is sent.
 493          self::$supportuser->emailstop = 0;
 494          return self::$supportuser;
 495      }
 496  
 497      /**
 498       * Reset self::$noreplyuser and self::$supportuser.
 499       * This is only used by phpunit, and there is no other use case for this function.
 500       * Please don't use it outside phpunit.
 501       */
 502      public static function reset_internal_users() {
 503          if (PHPUNIT_TEST) {
 504              self::$noreplyuser = false;
 505              self::$supportuser = false;
 506          } else {
 507              debugging('reset_internal_users() should not be used outside phpunit.', DEBUG_DEVELOPER);
 508          }
 509      }
 510  
 511      /**
 512       * Return true if user id is greater than 0 and alternatively check db.
 513       *
 514       * @param int $userid user id.
 515       * @param bool $checkdb if true userid will be checked in db. By default it's false, and
 516       *                      userid is compared with 0 for performance.
 517       * @return bool true is real user else false.
 518       */
 519      public static function is_real_user($userid, $checkdb = false) {
 520          global $DB;
 521  
 522          if ($userid <= 0) {
 523              return false;
 524          }
 525          if ($checkdb) {
 526              return $DB->record_exists('user', array('id' => $userid));
 527          } else {
 528              return true;
 529          }
 530      }
 531  
 532      /**
 533       * Determine whether the given user ID is that of the current user. Useful for components implementing permission callbacks
 534       * for preferences consumed by {@see fill_preferences_cache}
 535       *
 536       * @param stdClass $user
 537       * @return bool
 538       */
 539      public static function is_current_user(stdClass $user): bool {
 540          global $USER;
 541          return $user->id == $USER->id;
 542      }
 543  
 544      /**
 545       * Check if the given user is an active user in the site.
 546       *
 547       * @param  stdClass  $user         user object
 548       * @param  boolean $checksuspended whether to check if the user has the account suspended
 549       * @param  boolean $checknologin   whether to check if the user uses the nologin auth method
 550       * @throws moodle_exception
 551       * @since  Moodle 3.0
 552       */
 553      public static function require_active_user($user, $checksuspended = false, $checknologin = false) {
 554  
 555          if (!self::is_real_user($user->id)) {
 556              throw new moodle_exception('invaliduser', 'error');
 557          }
 558  
 559          if ($user->deleted) {
 560              throw new moodle_exception('userdeleted');
 561          }
 562  
 563          if (empty($user->confirmed)) {
 564              throw new moodle_exception('usernotconfirmed', 'moodle', '', $user->username);
 565          }
 566  
 567          if (isguestuser($user)) {
 568              throw new moodle_exception('guestsarenotallowed', 'error');
 569          }
 570  
 571          if ($checksuspended and $user->suspended) {
 572              throw new moodle_exception('suspended', 'auth');
 573          }
 574  
 575          if ($checknologin and $user->auth == 'nologin') {
 576              throw new moodle_exception('suspended', 'auth');
 577          }
 578      }
 579  
 580      /**
 581       * Updates the provided users profile picture based upon the expected fields returned from the edit or edit_advanced forms.
 582       *
 583       * @param stdClass $usernew An object that contains some information about the user being updated
 584       * @param array $filemanageroptions
 585       * @return bool True if the user was updated, false if it stayed the same.
 586       */
 587      public static function update_picture(stdClass $usernew, $filemanageroptions = array()) {
 588          global $CFG, $DB;
 589          require_once("$CFG->libdir/gdlib.php");
 590  
 591          $context = context_user::instance($usernew->id, MUST_EXIST);
 592          $user = core_user::get_user($usernew->id, 'id, picture', MUST_EXIST);
 593  
 594          $newpicture = $user->picture;
 595          // Get file_storage to process files.
 596          $fs = get_file_storage();
 597          if (!empty($usernew->deletepicture)) {
 598              // The user has chosen to delete the selected users picture.
 599              $fs->delete_area_files($context->id, 'user', 'icon'); // Drop all images in area.
 600              $newpicture = 0;
 601          }
 602  
 603          // Save newly uploaded file, this will avoid context mismatch for newly created users.
 604          if (!isset($usernew->imagefile)) {
 605              $usernew->imagefile = 0;
 606          }
 607          file_save_draft_area_files($usernew->imagefile, $context->id, 'user', 'newicon', 0, $filemanageroptions);
 608          if (($iconfiles = $fs->get_area_files($context->id, 'user', 'newicon')) && count($iconfiles) == 2) {
 609              // Get file which was uploaded in draft area.
 610              foreach ($iconfiles as $file) {
 611                  if (!$file->is_directory()) {
 612                      break;
 613                  }
 614              }
 615              // Copy file to temporary location and the send it for processing icon.
 616              if ($iconfile = $file->copy_content_to_temp()) {
 617                  // There is a new image that has been uploaded.
 618                  // Process the new image and set the user to make use of it.
 619                  // NOTE: Uploaded images always take over Gravatar.
 620                  $newpicture = (int)process_new_icon($context, 'user', 'icon', 0, $iconfile);
 621                  // Delete temporary file.
 622                  @unlink($iconfile);
 623                  // Remove uploaded file.
 624                  $fs->delete_area_files($context->id, 'user', 'newicon');
 625              } else {
 626                  // Something went wrong while creating temp file.
 627                  // Remove uploaded file.
 628                  $fs->delete_area_files($context->id, 'user', 'newicon');
 629                  return false;
 630              }
 631          }
 632  
 633          if ($newpicture != $user->picture) {
 634              $DB->set_field('user', 'picture', $newpicture, array('id' => $user->id));
 635              return true;
 636          } else {
 637              return false;
 638          }
 639      }
 640  
 641  
 642  
 643      /**
 644       * Definition of user profile fields and the expected parameter type for data validation.
 645       *
 646       * array(
 647       *     'property_name' => array(       // The user property to be checked. Should match the field on the user table.
 648       *          'null' => NULL_ALLOWED,    // Defaults to NULL_NOT_ALLOWED. Takes NULL_NOT_ALLOWED or NULL_ALLOWED.
 649       *          'type' => PARAM_TYPE,      // Expected parameter type of the user field.
 650       *          'choices' => array(1, 2..) // An array of accepted values of the user field.
 651       *          'default' => $CFG->setting // An default value for the field.
 652       *     )
 653       * )
 654       *
 655       * The fields choices and default are optional.
 656       *
 657       * @return void
 658       */
 659      protected static function fill_properties_cache() {
 660          global $CFG, $SESSION;
 661          if (self::$propertiescache !== null) {
 662              return;
 663          }
 664  
 665          // Array of user fields properties and expected parameters.
 666          // Every new field on the user table should be added here otherwise it won't be validated.
 667          $fields = array();
 668          $fields['id'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 669          $fields['auth'] = array('type' => PARAM_AUTH, 'null' => NULL_NOT_ALLOWED);
 670          $fields['confirmed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
 671          $fields['policyagreed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
 672          $fields['deleted'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
 673          $fields['suspended'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED);
 674          $fields['mnethostid'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 675          $fields['username'] = array('type' => PARAM_USERNAME, 'null' => NULL_NOT_ALLOWED);
 676          $fields['password'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
 677          $fields['idnumber'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
 678          $fields['firstname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 679          $fields['lastname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 680          $fields['surname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 681          $fields['email'] = array('type' => PARAM_RAW_TRIMMED, 'null' => NULL_NOT_ALLOWED);
 682          $fields['emailstop'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0);
 683          $fields['phone1'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 684          $fields['phone2'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 685          $fields['institution'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
 686          $fields['department'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
 687          $fields['address'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED);
 688          $fields['city'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->defaultcity);
 689          $fields['country'] = array('type' => PARAM_ALPHA, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->country,
 690                  'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_countries(true, true)));
 691          $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED,
 692                  'default' => (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang,
 693                  'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_translations(false)));
 694          $fields['calendartype'] = array('type' => PARAM_PLUGIN, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype,
 695                  'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types()));
 696          $fields['theme'] = array('type' => PARAM_THEME, 'null' => NULL_NOT_ALLOWED,
 697                  'default' => theme_config::DEFAULT_THEME, 'choices' => array_merge(array('' => ''), get_list_of_themes()));
 698          $fields['timezone'] = array('type' => PARAM_TIMEZONE, 'null' => NULL_NOT_ALLOWED,
 699                  'default' => core_date::get_server_timezone()); // Must not use choices here: timezones can come and go.
 700          $fields['firstaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 701          $fields['lastaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 702          $fields['lastlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 703          $fields['currentlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 704          $fields['lastip'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
 705          $fields['secret'] = array('type' => PARAM_ALPHANUM, 'null' => NULL_NOT_ALLOWED);
 706          $fields['picture'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 707          $fields['description'] = array('type' => PARAM_RAW, 'null' => NULL_ALLOWED);
 708          $fields['descriptionformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 709          $fields['mailformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
 710                  'default' => $CFG->defaultpreference_mailformat);
 711          $fields['maildigest'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
 712                  'default' => $CFG->defaultpreference_maildigest);
 713          $fields['maildisplay'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
 714                  'default' => $CFG->defaultpreference_maildisplay);
 715          $fields['autosubscribe'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
 716                  'default' => $CFG->defaultpreference_autosubscribe);
 717          $fields['trackforums'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED,
 718                  'default' => $CFG->defaultpreference_trackforums);
 719          $fields['timecreated'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 720          $fields['timemodified'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 721          $fields['trustbitmask'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
 722          $fields['imagealt'] = array('type' => PARAM_TEXT, 'null' => NULL_ALLOWED);
 723          $fields['lastnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
 724          $fields['firstnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
 725          $fields['middlename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
 726          $fields['alternatename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED);
 727  
 728          self::$propertiescache = $fields;
 729      }
 730  
 731      /**
 732       * Get properties of a user field.
 733       *
 734       * @param string $property property name to be retrieved.
 735       * @throws coding_exception if the requested property name is invalid.
 736       * @return array the property definition.
 737       */
 738      public static function get_property_definition($property) {
 739  
 740          self::fill_properties_cache();
 741  
 742          if (!array_key_exists($property, self::$propertiescache)) {
 743              throw new coding_exception('Invalid property requested.');
 744          }
 745  
 746          return self::$propertiescache[$property];
 747      }
 748  
 749      /**
 750       * Validate user data.
 751       *
 752       * This method just validates each user field and return an array of errors. It doesn't clean the data,
 753       * the methods clean() and clean_field() should be used for this purpose.
 754       *
 755       * @param stdClass|array $data user data object or array to be validated.
 756       * @return array|true $errors array of errors found on the user object, true if the validation passed.
 757       */
 758      public static function validate($data) {
 759          // Get all user profile fields definition.
 760          self::fill_properties_cache();
 761  
 762          foreach ($data as $property => $value) {
 763              try {
 764                  if (isset(self::$propertiescache[$property])) {
 765                      validate_param($value, self::$propertiescache[$property]['type'], self::$propertiescache[$property]['null']);
 766                  }
 767                  // Check that the value is part of a list of allowed values.
 768                  if (!empty(self::$propertiescache[$property]['choices']) &&
 769                          !isset(self::$propertiescache[$property]['choices'][$value])) {
 770                      throw new invalid_parameter_exception($value);
 771                  }
 772              } catch (invalid_parameter_exception $e) {
 773                  $errors[$property] = $e->getMessage();
 774              }
 775          }
 776  
 777          return empty($errors) ? true : $errors;
 778      }
 779  
 780      /**
 781       * Clean the properties cache.
 782       *
 783       * During unit tests we need to be able to reset all caches so that each new test starts in a known state.
 784       * Intended for use only for testing, phpunit calls this before every test.
 785       */
 786      public static function reset_caches() {
 787          self::$propertiescache = null;
 788      }
 789  
 790      /**
 791       * Clean the user data.
 792       *
 793       * @param stdClass|array $user the user data to be validated against properties definition.
 794       * @return stdClass $user the cleaned user data.
 795       */
 796      public static function clean_data($user) {
 797          if (empty($user)) {
 798              return $user;
 799          }
 800  
 801          foreach ($user as $field => $value) {
 802              // Get the property parameter type and do the cleaning.
 803              try {
 804                  $user->$field = core_user::clean_field($value, $field);
 805              } catch (coding_exception $e) {
 806                  debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
 807              }
 808          }
 809  
 810          return $user;
 811      }
 812  
 813      /**
 814       * Clean a specific user field.
 815       *
 816       * @param string $data the user field data to be cleaned.
 817       * @param string $field the user field name on the property definition cache.
 818       * @return string the cleaned user data.
 819       */
 820      public static function clean_field($data, $field) {
 821          if (empty($data) || empty($field)) {
 822              return $data;
 823          }
 824  
 825          try {
 826              $type = core_user::get_property_type($field);
 827  
 828              if (isset(self::$propertiescache[$field]['choices'])) {
 829                  if (!array_key_exists($data, self::$propertiescache[$field]['choices'])) {
 830                      if (isset(self::$propertiescache[$field]['default'])) {
 831                          $data = self::$propertiescache[$field]['default'];
 832                      } else {
 833                          $data = '';
 834                      }
 835                  } else {
 836                      return $data;
 837                  }
 838              } else {
 839                  $data = clean_param($data, $type);
 840              }
 841          } catch (coding_exception $e) {
 842              debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
 843          }
 844  
 845          return $data;
 846      }
 847  
 848      /**
 849       * Get the parameter type of the property.
 850       *
 851       * @param string $property property name to be retrieved.
 852       * @throws coding_exception if the requested property name is invalid.
 853       * @return int the property parameter type.
 854       */
 855      public static function get_property_type($property) {
 856  
 857          self::fill_properties_cache();
 858  
 859          if (!array_key_exists($property, self::$propertiescache)) {
 860              throw new coding_exception('Invalid property requested: ' . $property);
 861          }
 862  
 863          return self::$propertiescache[$property]['type'];
 864      }
 865  
 866      /**
 867       * Discover if the property is NULL_ALLOWED or NULL_NOT_ALLOWED.
 868       *
 869       * @param string $property property name to be retrieved.
 870       * @throws coding_exception if the requested property name is invalid.
 871       * @return bool true if the property is NULL_ALLOWED, false otherwise.
 872       */
 873      public static function get_property_null($property) {
 874  
 875          self::fill_properties_cache();
 876  
 877          if (!array_key_exists($property, self::$propertiescache)) {
 878              throw new coding_exception('Invalid property requested: ' . $property);
 879          }
 880  
 881          return self::$propertiescache[$property]['null'];
 882      }
 883  
 884      /**
 885       * Get the choices of the property.
 886       *
 887       * This is a helper method to validate a value against a list of acceptable choices.
 888       * For instance: country, language, themes and etc.
 889       *
 890       * @param string $property property name to be retrieved.
 891       * @throws coding_exception if the requested property name is invalid or if it does not has a list of choices.
 892       * @return array the property parameter type.
 893       */
 894      public static function get_property_choices($property) {
 895  
 896          self::fill_properties_cache();
 897  
 898          if (!array_key_exists($property, self::$propertiescache) && !array_key_exists('choices',
 899                  self::$propertiescache[$property])) {
 900  
 901              throw new coding_exception('Invalid property requested, or the property does not has a list of choices.');
 902          }
 903  
 904          return self::$propertiescache[$property]['choices'];
 905      }
 906  
 907      /**
 908       * Get the property default.
 909       *
 910       * This method gets the default value of a field (if exists).
 911       *
 912       * @param string $property property name to be retrieved.
 913       * @throws coding_exception if the requested property name is invalid or if it does not has a default value.
 914       * @return string the property default value.
 915       */
 916      public static function get_property_default($property) {
 917  
 918          self::fill_properties_cache();
 919  
 920          if (!array_key_exists($property, self::$propertiescache) || !isset(self::$propertiescache[$property]['default'])) {
 921              throw new coding_exception('Invalid property requested, or the property does not has a default value.');
 922          }
 923  
 924          return self::$propertiescache[$property]['default'];
 925      }
 926  
 927      /**
 928       * Definition of updateable user preferences and rules for data and access validation.
 929       *
 930       * array(
 931       *     'preferencename' => array(      // Either exact preference name or a regular expression.
 932       *          'null' => NULL_ALLOWED,    // Defaults to NULL_NOT_ALLOWED. Takes NULL_NOT_ALLOWED or NULL_ALLOWED.
 933       *          'type' => PARAM_TYPE,      // Expected parameter type of the user field - mandatory
 934       *          'choices' => array(1, 2..) // An array of accepted values of the user field - optional
 935       *          'default' => $CFG->setting // An default value for the field - optional
 936       *          'isregex' => false/true    // Whether the name of the preference is a regular expression (default false).
 937       *          'permissioncallback' => callable // Function accepting arguments ($user, $preferencename) that checks if current user
 938       *                                     // is allowed to modify this preference for given user.
 939       *                                     // If not specified core_user::default_preference_permission_check() will be assumed.
 940       *          'cleancallback' => callable // Custom callback for cleaning value if something more difficult than just type/choices is needed
 941       *                                     // accepts arguments ($value, $preferencename)
 942       *     )
 943       * )
 944       *
 945       * @return void
 946       */
 947      protected static function fill_preferences_cache() {
 948          global $CFG;
 949  
 950          if (self::$preferencescache !== null) {
 951              return;
 952          }
 953  
 954          // Array of user preferences and expected types/values.
 955          // Every preference that can be updated directly by user should be added here.
 956          $preferences = array();
 957          $preferences['auth_forcepasswordchange'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'choices' => array(0, 1),
 958              'permissioncallback' => function($user, $preferencename) {
 959                  global $USER;
 960                  $systemcontext = context_system::instance();
 961                  return ($USER->id != $user->id && (has_capability('moodle/user:update', $systemcontext) ||
 962                          ($user->timecreated > time() - 10 && has_capability('moodle/user:create', $systemcontext))));
 963              });
 964          $preferences['forum_markasreadonnotification'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1,
 965              'choices' => array(0, 1));
 966          $preferences['htmleditor'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED,
 967              'cleancallback' => function($value, $preferencename) {
 968                  if (empty($value) || !array_key_exists($value, core_component::get_plugin_list('editor'))) {
 969                      return null;
 970                  }
 971                  return $value;
 972              });
 973          $preferences['badgeprivacysetting'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1,
 974              'choices' => array(0, 1), 'permissioncallback' => function($user, $preferencename) {
 975                  global $CFG;
 976                  return !empty($CFG->enablebadges) && self::is_current_user($user);
 977              });
 978          $preferences['blogpagesize'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 10,
 979              'permissioncallback' => function($user, $preferencename) {
 980                  return self::is_current_user($user) && has_capability('moodle/blog:view', context_system::instance());
 981              });
 982  
 983          $choices = [HOMEPAGE_SITE];
 984          if (!empty($CFG->enabledashboard)) {
 985              $choices[] = HOMEPAGE_MY;
 986          }
 987          $choices[] = HOMEPAGE_MYCOURSES;
 988          $preferences['user_home_page_preference'] = [
 989              'type' => PARAM_INT,
 990              'null' => NULL_ALLOWED,
 991              'default' => get_default_home_page(),
 992              'choices' => $choices,
 993              'permissioncallback' => function ($user, $preferencename) {
 994                  global $CFG;
 995                  return self::is_current_user($user) &&
 996                      (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_USER));
 997              }
 998          ];
 999  
1000          // Core components that may want to define their preferences.
1001          // List of core components implementing callback is hardcoded here for performance reasons.
1002          // TODO MDL-58184 cache list of core components implementing a function.
1003          $corecomponents = ['core_message', 'core_calendar', 'core_contentbank'];
1004          foreach ($corecomponents as $component) {
1005              if (($pluginpreferences = component_callback($component, 'user_preferences')) && is_array($pluginpreferences)) {
1006                  $preferences += $pluginpreferences;
1007              }
1008          }
1009  
1010          // Plugins that may define their preferences.
1011          if ($pluginsfunction = get_plugins_with_function('user_preferences')) {
1012              foreach ($pluginsfunction as $plugintype => $plugins) {
1013                  foreach ($plugins as $function) {
1014                      if (($pluginpreferences = call_user_func($function)) && is_array($pluginpreferences)) {
1015                          $preferences += $pluginpreferences;
1016                      }
1017                  }
1018              }
1019          }
1020  
1021          self::$preferencescache = $preferences;
1022      }
1023  
1024      /**
1025       * Retrieves the preference definition
1026       *
1027       * @param string $preferencename
1028       * @return array
1029       */
1030      protected static function get_preference_definition($preferencename) {
1031          self::fill_preferences_cache();
1032  
1033          foreach (self::$preferencescache as $key => $preference) {
1034              if (empty($preference['isregex'])) {
1035                  if ($key === $preferencename) {
1036                      return $preference;
1037                  }
1038              } else {
1039                  if (preg_match($key, $preferencename)) {
1040                      return $preference;
1041                  }
1042              }
1043          }
1044  
1045          throw new coding_exception('Invalid preference requested.');
1046      }
1047  
1048      /**
1049       * Default callback used for checking if current user is allowed to change permission of user $user
1050       *
1051       * @param stdClass $user
1052       * @param string $preferencename
1053       * @return bool
1054       */
1055      protected static function default_preference_permission_check($user, $preferencename) {
1056          global $USER;
1057          if (is_mnet_remote_user($user)) {
1058              // Can't edit MNET user.
1059              return false;
1060          }
1061  
1062          if (self::is_current_user($user)) {
1063              // Editing own profile.
1064              $systemcontext = context_system::instance();
1065              return has_capability('moodle/user:editownprofile', $systemcontext);
1066          } else  {
1067              // Teachers, parents, etc.
1068              $personalcontext = context_user::instance($user->id);
1069              if (!has_capability('moodle/user:editprofile', $personalcontext)) {
1070                  return false;
1071              }
1072              if (is_siteadmin($user->id) and !is_siteadmin($USER)) {
1073                  // Only admins may edit other admins.
1074                  return false;
1075              }
1076              return true;
1077          }
1078      }
1079  
1080      /**
1081       * Can current user edit preference of this/another user
1082       *
1083       * @param string $preferencename
1084       * @param stdClass $user
1085       * @return bool
1086       */
1087      public static function can_edit_preference($preferencename, $user) {
1088          if (!isloggedin() || isguestuser()) {
1089              // Guests can not edit anything.
1090              return false;
1091          }
1092  
1093          try {
1094              $definition = self::get_preference_definition($preferencename);
1095          } catch (coding_exception $e) {
1096              return false;
1097          }
1098  
1099          if ($user->deleted || !context_user::instance($user->id, IGNORE_MISSING)) {
1100              // User is deleted.
1101              return false;
1102          }
1103  
1104          if (isset($definition['permissioncallback'])) {
1105              $callback = $definition['permissioncallback'];
1106              if (is_callable($callback)) {
1107                  return call_user_func_array($callback, [$user, $preferencename]);
1108              } else {
1109                  throw new coding_exception('Permission callback for preference ' . s($preferencename) . ' is not callable');
1110                  return false;
1111              }
1112          } else {
1113              return self::default_preference_permission_check($user, $preferencename);
1114          }
1115      }
1116  
1117      /**
1118       * Clean value of a user preference
1119       *
1120       * @param string $value the user preference value to be cleaned.
1121       * @param string $preferencename the user preference name
1122       * @return string the cleaned preference value
1123       */
1124      public static function clean_preference($value, $preferencename) {
1125  
1126          $definition = self::get_preference_definition($preferencename);
1127  
1128          if (isset($definition['type']) && $value !== null) {
1129              $value = clean_param($value, $definition['type']);
1130          }
1131  
1132          if (isset($definition['cleancallback'])) {
1133              $callback = $definition['cleancallback'];
1134              if (is_callable($callback)) {
1135                  return $callback($value, $preferencename);
1136              } else {
1137                  throw new coding_exception('Clean callback for preference ' . s($preferencename) . ' is not callable');
1138              }
1139          } else if ($value === null && (!isset($definition['null']) || $definition['null'] == NULL_ALLOWED)) {
1140              return null;
1141          } else if (isset($definition['choices'])) {
1142              if (!in_array($value, $definition['choices'])) {
1143                  if (isset($definition['default'])) {
1144                      return $definition['default'];
1145                  } else {
1146                      $first = reset($definition['choices']);
1147                      return $first;
1148                  }
1149              } else {
1150                  return $value;
1151              }
1152          } else {
1153              if ($value === null) {
1154                  return isset($definition['default']) ? $definition['default'] : '';
1155              }
1156              return $value;
1157          }
1158      }
1159  
1160      /**
1161       * Is the user expected to perform an action to start using Moodle properly?
1162       *
1163       * This covers cases such as filling the profile, changing password or agreeing to the site policy.
1164       *
1165       * @param stdClass $user User object, defaults to the current user.
1166       * @return bool
1167       */
1168      public static function awaiting_action(stdClass $user = null): bool {
1169          global $USER;
1170  
1171          if ($user === null) {
1172              $user = $USER;
1173          }
1174  
1175          if (user_not_fully_set_up($user)) {
1176              // Awaiting the user to fill all fields in the profile.
1177              return true;
1178          }
1179  
1180          if (get_user_preferences('auth_forcepasswordchange', false, $user)) {
1181              // Awaiting the user to change their password.
1182              return true;
1183          }
1184  
1185          if (empty($user->policyagreed) && !is_siteadmin($user)) {
1186              $manager = new \core_privacy\local\sitepolicy\manager();
1187  
1188              if ($manager->is_defined(isguestuser($user))) {
1189                  return true;
1190              }
1191          }
1192  
1193          return false;
1194      }
1195  
1196      /**
1197       * Get welcome message.
1198       *
1199       * @return lang_string welcome message
1200       */
1201      public static function welcome_message(): ?lang_string {
1202          global $USER;
1203  
1204          $isloggedinas = \core\session\manager::is_loggedinas();
1205          if (!isloggedin() || isguestuser() || $isloggedinas) {
1206              return null;
1207          }
1208          if (empty($USER->core_welcome_message)) {
1209              $USER->core_welcome_message = true;
1210              $messagekey = 'welcomeback';
1211              if (empty(get_user_preferences('core_user_welcome', null))) {
1212                  $messagekey = 'welcometosite';
1213                  set_user_preference('core_user_welcome', time());
1214              }
1215  
1216              $namefields = [
1217                  'fullname' => fullname($USER),
1218                  'alternativefullname' => fullname($USER, true),
1219              ];
1220  
1221              foreach (\core_user\fields::get_name_fields() as $namefield) {
1222                  $namefields[$namefield] = $USER->{$namefield};
1223              }
1224  
1225              return new lang_string($messagekey, 'core', $namefields);
1226          };
1227          return null;
1228      }
1229  
1230  
1231      /**
1232       * Get initials for users
1233       *
1234       * @param stdClass $user
1235       * @return string
1236       */
1237      public static function get_initials(stdClass $user): string {
1238          // Get the available name fields.
1239          $namefields = \core_user\fields::get_name_fields();
1240          // Build a dummy user to determine the name format.
1241          $dummyuser = array_combine($namefields, $namefields);
1242          // Determine the name format by using fullname() and passing the dummy user.
1243          $nameformat = fullname((object) $dummyuser);
1244          // Fetch all the available username fields.
1245          $availablefields = order_in_string($namefields, $nameformat);
1246          // We only want the first and last name fields.
1247          if (!empty($availablefields) && count($availablefields) >= 2) {
1248              $availablefields = [reset($availablefields), end($availablefields)];
1249          }
1250          $initials = '';
1251          foreach ($availablefields as $userfieldname) {
1252              $initials .= mb_substr($user->$userfieldname, 0, 1);
1253          }
1254          return $initials;
1255      }
1256  }