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 402] [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   * External user API
  19   *
  20   * @package    core_user
  21   * @category   external
  22   * @copyright  2009 Petr Skodak
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  require_once("$CFG->libdir/externallib.php");
  28  
  29  /**
  30   * User external functions
  31   *
  32   * @package    core_user
  33   * @category   external
  34   * @copyright  2011 Jerome Mouneyrac
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   * @since Moodle 2.2
  37   */
  38  class core_user_external extends external_api {
  39  
  40      /**
  41       * Returns description of method parameters
  42       *
  43       * @return external_function_parameters
  44       * @since Moodle 2.2
  45       */
  46      public static function create_users_parameters() {
  47          global $CFG;
  48          $userfields = [
  49              'createpassword' => new external_value(PARAM_BOOL, 'True if password should be created and mailed to user.',
  50                  VALUE_OPTIONAL),
  51              // General.
  52              'username' => new external_value(core_user::get_property_type('username'),
  53                  'Username policy is defined in Moodle security config.'),
  54              'auth' => new external_value(core_user::get_property_type('auth'), 'Auth plugins include manual, ldap, etc',
  55                  VALUE_DEFAULT, 'manual', core_user::get_property_null('auth')),
  56              'password' => new external_value(core_user::get_property_type('password'),
  57                  'Plain text password consisting of any characters', VALUE_OPTIONAL),
  58              'firstname' => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user'),
  59              'lastname' => new external_value(core_user::get_property_type('lastname'), 'The family name of the user'),
  60              'email' => new external_value(core_user::get_property_type('email'), 'A valid and unique email address'),
  61              'maildisplay' => new external_value(core_user::get_property_type('maildisplay'), 'Email visibility', VALUE_OPTIONAL),
  62              'city' => new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_OPTIONAL),
  63              'country' => new external_value(core_user::get_property_type('country'),
  64                  'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
  65              'timezone' => new external_value(core_user::get_property_type('timezone'),
  66                  'Timezone code such as Australia/Perth, or 99 for default', VALUE_OPTIONAL),
  67              'description' => new external_value(core_user::get_property_type('description'), 'User profile description, no HTML',
  68                  VALUE_OPTIONAL),
  69              // Additional names.
  70              'firstnamephonetic' => new external_value(core_user::get_property_type('firstnamephonetic'),
  71                  'The first name(s) phonetically of the user', VALUE_OPTIONAL),
  72              'lastnamephonetic' => new external_value(core_user::get_property_type('lastnamephonetic'),
  73                  'The family name phonetically of the user', VALUE_OPTIONAL),
  74              'middlename' => new external_value(core_user::get_property_type('middlename'), 'The middle name of the user',
  75                  VALUE_OPTIONAL),
  76              'alternatename' => new external_value(core_user::get_property_type('alternatename'), 'The alternate name of the user',
  77                  VALUE_OPTIONAL),
  78              // Interests.
  79              'interests' => new external_value(PARAM_TEXT, 'User interests (separated by commas)', VALUE_OPTIONAL),
  80              // Optional.
  81              'idnumber' => new external_value(core_user::get_property_type('idnumber'),
  82                  'An arbitrary ID code number perhaps from the institution', VALUE_DEFAULT, ''),
  83              'institution' => new external_value(core_user::get_property_type('institution'), 'institution', VALUE_OPTIONAL),
  84              'department' => new external_value(core_user::get_property_type('department'), 'department', VALUE_OPTIONAL),
  85              'phone1' => new external_value(core_user::get_property_type('phone1'), 'Phone 1', VALUE_OPTIONAL),
  86              'phone2' => new external_value(core_user::get_property_type('phone2'), 'Phone 2', VALUE_OPTIONAL),
  87              'address' => new external_value(core_user::get_property_type('address'), 'Postal address', VALUE_OPTIONAL),
  88              // Other user preferences stored in the user table.
  89              'lang' => new external_value(core_user::get_property_type('lang'), 'Language code such as "en", must exist on server',
  90                  VALUE_DEFAULT, core_user::get_property_default('lang'), core_user::get_property_null('lang')),
  91              'calendartype' => new external_value(core_user::get_property_type('calendartype'),
  92                  'Calendar type such as "gregorian", must exist on server', VALUE_DEFAULT, $CFG->calendartype, VALUE_OPTIONAL),
  93              'theme' => new external_value(core_user::get_property_type('theme'),
  94                  'Theme name such as "standard", must exist on server', VALUE_OPTIONAL),
  95              'mailformat' => new external_value(core_user::get_property_type('mailformat'),
  96                  'Mail format code is 0 for plain text, 1 for HTML etc', VALUE_OPTIONAL),
  97              // Custom user profile fields.
  98              'customfields' => new external_multiple_structure(
  99                  new external_single_structure(
 100                      [
 101                          'type'  => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'),
 102                          'value' => new external_value(PARAM_RAW, 'The value of the custom field')
 103                      ]
 104                  ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
 105              // User preferences.
 106              'preferences' => new external_multiple_structure(
 107              new external_single_structure(
 108                  [
 109                      'type'  => new external_value(PARAM_RAW, 'The name of the preference'),
 110                      'value' => new external_value(PARAM_RAW, 'The value of the preference')
 111                  ]
 112              ), 'User preferences', VALUE_OPTIONAL),
 113          ];
 114          return new external_function_parameters(
 115              [
 116                  'users' => new external_multiple_structure(
 117                      new external_single_structure($userfields)
 118                  )
 119              ]
 120          );
 121      }
 122  
 123      /**
 124       * Create one or more users.
 125       *
 126       * @throws invalid_parameter_exception
 127       * @param array $users An array of users to create.
 128       * @return array An array of arrays
 129       * @since Moodle 2.2
 130       */
 131      public static function create_users($users) {
 132          global $CFG, $DB;
 133          require_once($CFG->dirroot."/lib/weblib.php");
 134          require_once($CFG->dirroot."/user/lib.php");
 135          require_once($CFG->dirroot."/user/editlib.php");
 136          require_once($CFG->dirroot."/user/profile/lib.php"); // Required for customfields related function.
 137  
 138          // Ensure the current user is allowed to run this function.
 139          $context = context_system::instance();
 140          self::validate_context($context);
 141          require_capability('moodle/user:create', $context);
 142  
 143          // Do basic automatic PARAM checks on incoming data, using params description.
 144          // If any problems are found then exceptions are thrown with helpful error messages.
 145          $params = self::validate_parameters(self::create_users_parameters(), array('users' => $users));
 146  
 147          $availableauths  = core_component::get_plugin_list('auth');
 148          unset($availableauths['mnet']);       // These would need mnethostid too.
 149          unset($availableauths['webservice']); // We do not want new webservice users for now.
 150  
 151          $availablethemes = core_component::get_plugin_list('theme');
 152          $availablelangs  = get_string_manager()->get_list_of_translations();
 153  
 154          $transaction = $DB->start_delegated_transaction();
 155  
 156          $userids = array();
 157          foreach ($params['users'] as $user) {
 158              // Make sure that the username, firstname and lastname are not blank.
 159              foreach (array('username', 'firstname', 'lastname') as $fieldname) {
 160                  if (trim($user[$fieldname]) === '') {
 161                      throw new invalid_parameter_exception('The field '.$fieldname.' cannot be blank');
 162                  }
 163              }
 164  
 165              // Make sure that the username doesn't already exist.
 166              if ($DB->record_exists('user', array('username' => $user['username'], 'mnethostid' => $CFG->mnet_localhost_id))) {
 167                  throw new invalid_parameter_exception('Username already exists: '.$user['username']);
 168              }
 169  
 170              // Make sure auth is valid.
 171              if (empty($availableauths[$user['auth']])) {
 172                  throw new invalid_parameter_exception('Invalid authentication type: '.$user['auth']);
 173              }
 174  
 175              // Make sure lang is valid.
 176              if (empty($availablelangs[$user['lang']])) {
 177                  throw new invalid_parameter_exception('Invalid language code: '.$user['lang']);
 178              }
 179  
 180              // Make sure lang is valid.
 181              if (!empty($user['theme']) && empty($availablethemes[$user['theme']])) { // Theme is VALUE_OPTIONAL,
 182                                                                                       // so no default value
 183                                                                                       // We need to test if the client sent it
 184                                                                                       // => !empty($user['theme']).
 185                  throw new invalid_parameter_exception('Invalid theme: '.$user['theme']);
 186              }
 187  
 188              // Make sure we have a password or have to create one.
 189              $authplugin = get_auth_plugin($user['auth']);
 190              if ($authplugin->is_internal() && empty($user['password']) && empty($user['createpassword'])) {
 191                  throw new invalid_parameter_exception('Invalid password: you must provide a password, or set createpassword.');
 192              }
 193  
 194              $user['confirmed'] = true;
 195              $user['mnethostid'] = $CFG->mnet_localhost_id;
 196  
 197              // Start of user info validation.
 198              // Make sure we validate current user info as handled by current GUI. See user/editadvanced_form.php func validation().
 199              if (!validate_email($user['email'])) {
 200                  throw new invalid_parameter_exception('Email address is invalid: '.$user['email']);
 201              } else if (empty($CFG->allowaccountssameemail)) {
 202                  // Make a case-insensitive query for the given email address.
 203                  $select = $DB->sql_equal('email', ':email', false) . ' AND mnethostid = :mnethostid';
 204                  $params = array(
 205                      'email' => $user['email'],
 206                      'mnethostid' => $user['mnethostid']
 207                  );
 208                  // If there are other user(s) that already have the same email, throw an error.
 209                  if ($DB->record_exists_select('user', $select, $params)) {
 210                      throw new invalid_parameter_exception('Email address already exists: '.$user['email']);
 211                  }
 212              }
 213              // End of user info validation.
 214  
 215              $createpassword = !empty($user['createpassword']);
 216              unset($user['createpassword']);
 217              $updatepassword = false;
 218              if ($authplugin->is_internal()) {
 219                  if ($createpassword) {
 220                      $user['password'] = '';
 221                  } else {
 222                      $updatepassword = true;
 223                  }
 224              } else {
 225                  $user['password'] = AUTH_PASSWORD_NOT_CACHED;
 226              }
 227  
 228              // Create the user data now!
 229              $user['id'] = user_create_user($user, $updatepassword, false);
 230  
 231              $userobject = (object)$user;
 232  
 233              // Set user interests.
 234              if (!empty($user['interests'])) {
 235                  $trimmedinterests = array_map('trim', explode(',', $user['interests']));
 236                  $interests = array_filter($trimmedinterests, function($value) {
 237                      return !empty($value);
 238                  });
 239                  useredit_update_interests($userobject, $interests);
 240              }
 241  
 242              // Custom fields.
 243              if (!empty($user['customfields'])) {
 244                  foreach ($user['customfields'] as $customfield) {
 245                      // Profile_save_data() saves profile file it's expecting a user with the correct id,
 246                      // and custom field to be named profile_field_"shortname".
 247                      $user["profile_field_".$customfield['type']] = $customfield['value'];
 248                  }
 249                  profile_save_data((object) $user);
 250              }
 251  
 252              if ($createpassword) {
 253                  setnew_password_and_mail($userobject);
 254                  unset_user_preference('create_password', $userobject);
 255                  set_user_preference('auth_forcepasswordchange', 1, $userobject);
 256              }
 257  
 258              // Trigger event.
 259              \core\event\user_created::create_from_userid($user['id'])->trigger();
 260  
 261              // Preferences.
 262              if (!empty($user['preferences'])) {
 263                  $userpref = (object)$user;
 264                  foreach ($user['preferences'] as $preference) {
 265                      $userpref->{'preference_'.$preference['type']} = $preference['value'];
 266                  }
 267                  useredit_update_user_preference($userpref);
 268              }
 269  
 270              $userids[] = array('id' => $user['id'], 'username' => $user['username']);
 271          }
 272  
 273          $transaction->allow_commit();
 274  
 275          return $userids;
 276      }
 277  
 278      /**
 279       * Returns description of method result value
 280       *
 281       * @return external_description
 282       * @since Moodle 2.2
 283       */
 284      public static function create_users_returns() {
 285          return new external_multiple_structure(
 286              new external_single_structure(
 287                  array(
 288                      'id'       => new external_value(core_user::get_property_type('id'), 'user id'),
 289                      'username' => new external_value(core_user::get_property_type('username'), 'user name'),
 290                  )
 291              )
 292          );
 293      }
 294  
 295  
 296      /**
 297       * Returns description of method parameters
 298       *
 299       * @return external_function_parameters
 300       * @since Moodle 2.2
 301       */
 302      public static function delete_users_parameters() {
 303          return new external_function_parameters(
 304              array(
 305                  'userids' => new external_multiple_structure(new external_value(core_user::get_property_type('id'), 'user ID')),
 306              )
 307          );
 308      }
 309  
 310      /**
 311       * Delete users
 312       *
 313       * @throws moodle_exception
 314       * @param array $userids
 315       * @return null
 316       * @since Moodle 2.2
 317       */
 318      public static function delete_users($userids) {
 319          global $CFG, $DB, $USER;
 320          require_once($CFG->dirroot."/user/lib.php");
 321  
 322          // Ensure the current user is allowed to run this function.
 323          $context = context_system::instance();
 324          require_capability('moodle/user:delete', $context);
 325          self::validate_context($context);
 326  
 327          $params = self::validate_parameters(self::delete_users_parameters(), array('userids' => $userids));
 328  
 329          $transaction = $DB->start_delegated_transaction();
 330  
 331          foreach ($params['userids'] as $userid) {
 332              $user = $DB->get_record('user', array('id' => $userid, 'deleted' => 0), '*', MUST_EXIST);
 333              // Must not allow deleting of admins or self!!!
 334              if (is_siteadmin($user)) {
 335                  throw new moodle_exception('useradminodelete', 'error');
 336              }
 337              if ($USER->id == $user->id) {
 338                  throw new moodle_exception('usernotdeletederror', 'error');
 339              }
 340              user_delete_user($user);
 341          }
 342  
 343          $transaction->allow_commit();
 344  
 345          return null;
 346      }
 347  
 348      /**
 349       * Returns description of method result value
 350       *
 351       * @return null
 352       * @since Moodle 2.2
 353       */
 354      public static function delete_users_returns() {
 355          return null;
 356      }
 357  
 358      /**
 359       * Returns description of method parameters.
 360       *
 361       * @return external_function_parameters
 362       * @since Moodle 3.2
 363       */
 364      public static function update_user_preferences_parameters() {
 365          return new external_function_parameters(
 366              array(
 367                  'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0),
 368                  'emailstop' => new external_value(core_user::get_property_type('emailstop'),
 369                      'Enable or disable notifications for this user', VALUE_DEFAULT, null),
 370                  'preferences' => new external_multiple_structure(
 371                      new external_single_structure(
 372                          array(
 373                              'type'  => new external_value(PARAM_RAW, 'The name of the preference'),
 374                              'value' => new external_value(PARAM_RAW, 'The value of the preference, do not set this field if you
 375                                  want to remove (unset) the current value.', VALUE_DEFAULT, null),
 376                          )
 377                      ), 'User preferences', VALUE_DEFAULT, array()
 378                  )
 379              )
 380          );
 381      }
 382  
 383      /**
 384       * Update the user's preferences.
 385       *
 386       * @param int $userid
 387       * @param bool|null $emailstop
 388       * @param array $preferences
 389       * @return null
 390       * @since Moodle 3.2
 391       */
 392      public static function update_user_preferences($userid = 0, $emailstop = null, $preferences = array()) {
 393          global $USER, $CFG;
 394  
 395          require_once($CFG->dirroot . '/user/lib.php');
 396          require_once($CFG->dirroot . '/user/editlib.php');
 397          require_once($CFG->dirroot . '/message/lib.php');
 398  
 399          if (empty($userid)) {
 400              $userid = $USER->id;
 401          }
 402  
 403          $systemcontext = context_system::instance();
 404          self::validate_context($systemcontext);
 405          $params = array(
 406              'userid' => $userid,
 407              'emailstop' => $emailstop,
 408              'preferences' => $preferences
 409          );
 410          $params = self::validate_parameters(self::update_user_preferences_parameters(), $params);
 411          $preferences = $params['preferences'];
 412  
 413          // Preferences.
 414          if (!empty($preferences)) {
 415              $userpref = ['id' => $userid];
 416              foreach ($preferences as $preference) {
 417  
 418                  /*
 419                   * Rename user message provider preferences to avoid orphan settings on old app versions.
 420                   * @todo Remove this "translation" block on MDL-73284.
 421                   */
 422                  if (preg_match('/message_provider_.*_loggedin/', $preference['type']) ||
 423                          preg_match('/message_provider_.*_loggedoff/', $preference['type'])) {
 424                      $nameparts = explode('_', $preference['type']);
 425                      array_pop($nameparts);
 426                      $preference['type'] = implode('_', $nameparts).'_enabled';
 427                  }
 428  
 429                  $userpref['preference_' . $preference['type']] = $preference['value'];
 430              }
 431              useredit_update_user_preference($userpref);
 432          }
 433  
 434          // Check if they want to update the email.
 435          if ($emailstop !== null) {
 436              $otheruser = ($userid == $USER->id) ? $USER : core_user::get_user($userid, '*', MUST_EXIST);
 437              core_user::require_active_user($otheruser);
 438              if (core_message_can_edit_message_profile($otheruser) && $otheruser->emailstop != $emailstop) {
 439                  $user = new stdClass();
 440                  $user->id = $userid;
 441                  $user->emailstop = $emailstop;
 442                  user_update_user($user);
 443  
 444                  // Update the $USER if we should.
 445                  if ($userid == $USER->id) {
 446                      $USER->emailstop = $emailstop;
 447                  }
 448              }
 449          }
 450  
 451          return null;
 452      }
 453  
 454      /**
 455       * Returns description of method result value
 456       *
 457       * @return null
 458       * @since Moodle 3.2
 459       */
 460      public static function update_user_preferences_returns() {
 461          return null;
 462      }
 463  
 464      /**
 465       * Returns description of method parameters
 466       *
 467       * @return external_function_parameters
 468       * @since Moodle 2.2
 469       */
 470      public static function update_users_parameters() {
 471          $userfields = [
 472              'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
 473              // General.
 474              'username' => new external_value(core_user::get_property_type('username'),
 475                  'Username policy is defined in Moodle security config.', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 476              'auth' => new external_value(core_user::get_property_type('auth'), 'Auth plugins include manual, ldap, etc',
 477                  VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 478              'suspended' => new external_value(core_user::get_property_type('suspended'),
 479                  'Suspend user account, either false to enable user login or true to disable it', VALUE_OPTIONAL),
 480              'password' => new external_value(core_user::get_property_type('password'),
 481                  'Plain text password consisting of any characters', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 482              'firstname' => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user',
 483                  VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 484              'lastname' => new external_value(core_user::get_property_type('lastname'), 'The family name of the user',
 485                  VALUE_OPTIONAL),
 486              'email' => new external_value(core_user::get_property_type('email'), 'A valid and unique email address', VALUE_OPTIONAL,
 487                  '', NULL_NOT_ALLOWED),
 488              'maildisplay' => new external_value(core_user::get_property_type('maildisplay'), 'Email visibility', VALUE_OPTIONAL),
 489              'city' => new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_OPTIONAL),
 490              'country' => new external_value(core_user::get_property_type('country'),
 491                  'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
 492              'timezone' => new external_value(core_user::get_property_type('timezone'),
 493                  'Timezone code such as Australia/Perth, or 99 for default', VALUE_OPTIONAL),
 494              'description' => new external_value(core_user::get_property_type('description'), 'User profile description, no HTML',
 495                  VALUE_OPTIONAL),
 496              // User picture.
 497              'userpicture' => new external_value(PARAM_INT,
 498                  'The itemid where the new user picture has been uploaded to, 0 to delete', VALUE_OPTIONAL),
 499              // Additional names.
 500              'firstnamephonetic' => new external_value(core_user::get_property_type('firstnamephonetic'),
 501                  'The first name(s) phonetically of the user', VALUE_OPTIONAL),
 502              'lastnamephonetic' => new external_value(core_user::get_property_type('lastnamephonetic'),
 503                  'The family name phonetically of the user', VALUE_OPTIONAL),
 504              'middlename' => new external_value(core_user::get_property_type('middlename'), 'The middle name of the user',
 505                  VALUE_OPTIONAL),
 506              'alternatename' => new external_value(core_user::get_property_type('alternatename'), 'The alternate name of the user',
 507                  VALUE_OPTIONAL),
 508              // Interests.
 509              'interests' => new external_value(PARAM_TEXT, 'User interests (separated by commas)', VALUE_OPTIONAL),
 510              // Optional.
 511              'idnumber' => new external_value(core_user::get_property_type('idnumber'),
 512                  'An arbitrary ID code number perhaps from the institution', VALUE_OPTIONAL),
 513              'institution' => new external_value(core_user::get_property_type('institution'), 'Institution', VALUE_OPTIONAL),
 514              'department' => new external_value(core_user::get_property_type('department'), 'Department', VALUE_OPTIONAL),
 515              'phone1' => new external_value(core_user::get_property_type('phone1'), 'Phone', VALUE_OPTIONAL),
 516              'phone2' => new external_value(core_user::get_property_type('phone2'), 'Mobile phone', VALUE_OPTIONAL),
 517              'address' => new external_value(core_user::get_property_type('address'), 'Postal address', VALUE_OPTIONAL),
 518              // Other user preferences stored in the user table.
 519              'lang' => new external_value(core_user::get_property_type('lang'), 'Language code such as "en", must exist on server',
 520                  VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 521              'calendartype' => new external_value(core_user::get_property_type('calendartype'),
 522                  'Calendar type such as "gregorian", must exist on server', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
 523              'theme' => new external_value(core_user::get_property_type('theme'),
 524                  'Theme name such as "standard", must exist on server', VALUE_OPTIONAL),
 525              'mailformat' => new external_value(core_user::get_property_type('mailformat'),
 526                  'Mail format code is 0 for plain text, 1 for HTML etc', VALUE_OPTIONAL),
 527              // Custom user profile fields.
 528              'customfields' => new external_multiple_structure(
 529                  new external_single_structure(
 530                      [
 531                          'type'  => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'),
 532                          'value' => new external_value(PARAM_RAW, 'The value of the custom field')
 533                      ]
 534                  ), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
 535              // User preferences.
 536              'preferences' => new external_multiple_structure(
 537                  new external_single_structure(
 538                      [
 539                          'type'  => new external_value(PARAM_RAW, 'The name of the preference'),
 540                          'value' => new external_value(PARAM_RAW, 'The value of the preference')
 541                      ]
 542                  ), 'User preferences', VALUE_OPTIONAL),
 543          ];
 544          return new external_function_parameters(
 545              [
 546                  'users' => new external_multiple_structure(
 547                      new external_single_structure($userfields)
 548                  )
 549              ]
 550          );
 551      }
 552  
 553      /**
 554       * Update users
 555       *
 556       * @param array $users
 557       * @return null
 558       * @since Moodle 2.2
 559       */
 560      public static function update_users($users) {
 561          global $CFG, $DB, $USER;
 562          require_once($CFG->dirroot."/user/lib.php");
 563          require_once($CFG->dirroot."/user/profile/lib.php"); // Required for customfields related function.
 564          require_once($CFG->dirroot.'/user/editlib.php');
 565  
 566          // Ensure the current user is allowed to run this function.
 567          $context = context_system::instance();
 568          require_capability('moodle/user:update', $context);
 569          self::validate_context($context);
 570  
 571          $params = self::validate_parameters(self::update_users_parameters(), array('users' => $users));
 572  
 573          $filemanageroptions = array('maxbytes' => $CFG->maxbytes,
 574                  'subdirs'        => 0,
 575                  'maxfiles'       => 1,
 576                  'accepted_types' => 'optimised_image');
 577  
 578          $warnings = array();
 579          foreach ($params['users'] as $user) {
 580              // Catch any exception while updating a user and return it as a warning.
 581              try {
 582                  $transaction = $DB->start_delegated_transaction();
 583  
 584                  // First check the user exists.
 585                  if (!$existinguser = core_user::get_user($user['id'])) {
 586                      throw new moodle_exception('invaliduserid', '', '', null,
 587                              'Invalid user ID');
 588                  }
 589                  // Check if we are trying to update an admin.
 590                  if ($existinguser->id != $USER->id and is_siteadmin($existinguser) and !is_siteadmin($USER)) {
 591                      throw new moodle_exception('usernotupdatedadmin', '', '', null,
 592                              'Cannot update admin accounts');
 593                  }
 594                  // Other checks (deleted, remote or guest users).
 595                  if ($existinguser->deleted) {
 596                      throw new moodle_exception('usernotupdateddeleted', '', '', null,
 597                              'User is a deleted user');
 598                  }
 599                  if (is_mnet_remote_user($existinguser)) {
 600                      throw new moodle_exception('usernotupdatedremote', '', '', null,
 601                              'User is a remote user');
 602                  }
 603                  if (isguestuser($existinguser->id)) {
 604                      throw new moodle_exception('usernotupdatedguest', '', '', null,
 605                              'Cannot update guest account');
 606                  }
 607                  // Check duplicated emails.
 608                  if (isset($user['email']) && $user['email'] !== $existinguser->email) {
 609                      if (!validate_email($user['email'])) {
 610                          throw new moodle_exception('useremailinvalid', '', '', null,
 611                                  'Invalid email address');
 612                      } else if (empty($CFG->allowaccountssameemail)) {
 613                          // Make a case-insensitive query for the given email address
 614                          // and make sure to exclude the user being updated.
 615                          $select = $DB->sql_equal('email', ':email', false) . ' AND mnethostid = :mnethostid AND id <> :userid';
 616                          $params = array(
 617                              'email' => $user['email'],
 618                              'mnethostid' => $CFG->mnet_localhost_id,
 619                              'userid' => $user['id']
 620                          );
 621                          // Skip if there are other user(s) that already have the same email.
 622                          if ($DB->record_exists_select('user', $select, $params)) {
 623                              throw new moodle_exception('useremailduplicate', '', '', null,
 624                                      'Duplicate email address');
 625                          }
 626                      }
 627                  }
 628  
 629                  user_update_user($user, true, false);
 630  
 631                  $userobject = (object)$user;
 632  
 633                  // Update user picture if it was specified for this user.
 634                  if (empty($CFG->disableuserimages) && isset($user['userpicture'])) {
 635                      $userobject->deletepicture = null;
 636  
 637                      if ($user['userpicture'] == 0) {
 638                          $userobject->deletepicture = true;
 639                      } else {
 640                          $userobject->imagefile = $user['userpicture'];
 641                      }
 642  
 643                      core_user::update_picture($userobject, $filemanageroptions);
 644                  }
 645  
 646                  // Update user interests.
 647                  if (!empty($user['interests'])) {
 648                      $trimmedinterests = array_map('trim', explode(',', $user['interests']));
 649                      $interests = array_filter($trimmedinterests, function($value) {
 650                          return !empty($value);
 651                      });
 652                      useredit_update_interests($userobject, $interests);
 653                  }
 654  
 655                  // Update user custom fields.
 656                  if (!empty($user['customfields'])) {
 657  
 658                      foreach ($user['customfields'] as $customfield) {
 659                          // Profile_save_data() saves profile file it's expecting a user with the correct id,
 660                          // and custom field to be named profile_field_"shortname".
 661                          $user["profile_field_".$customfield['type']] = $customfield['value'];
 662                      }
 663                      profile_save_data((object) $user);
 664                  }
 665  
 666                  // Trigger event.
 667                  \core\event\user_updated::create_from_userid($user['id'])->trigger();
 668  
 669                  // Preferences.
 670                  if (!empty($user['preferences'])) {
 671                      $userpref = clone($existinguser);
 672                      foreach ($user['preferences'] as $preference) {
 673                          $userpref->{'preference_'.$preference['type']} = $preference['value'];
 674                      }
 675                      useredit_update_user_preference($userpref);
 676                  }
 677                  if (isset($user['suspended']) and $user['suspended']) {
 678                      \core\session\manager::kill_user_sessions($user['id']);
 679                  }
 680  
 681                  $transaction->allow_commit();
 682              } catch (Exception $e) {
 683                  try {
 684                      $transaction->rollback($e);
 685                  } catch (Exception $e) {
 686                      $warning = [];
 687                      $warning['item'] = 'user';
 688                      $warning['itemid'] = $user['id'];
 689                      if ($e instanceof moodle_exception) {
 690                          $warning['warningcode'] = $e->errorcode;
 691                      } else {
 692                          $warning['warningcode'] = $e->getCode();
 693                      }
 694                      $warning['message'] = $e->getMessage();
 695                      $warnings[] = $warning;
 696                  }
 697              }
 698          }
 699  
 700          return ['warnings' => $warnings];
 701      }
 702  
 703      /**
 704       * Returns description of method result value
 705       *
 706       * @return external_description
 707       * @since Moodle 2.2
 708       */
 709      public static function update_users_returns() {
 710          return new external_single_structure(
 711              array(
 712                  'warnings' => new external_warnings()
 713              )
 714          );
 715      }
 716  
 717      /**
 718       * Returns description of method parameters
 719       *
 720       * @return external_function_parameters
 721       * @since Moodle 2.4
 722       */
 723      public static function get_users_by_field_parameters() {
 724          return new external_function_parameters(
 725              array(
 726                  'field' => new external_value(PARAM_ALPHA, 'the search field can be
 727                      \'id\' or \'idnumber\' or \'username\' or \'email\''),
 728                  'values' => new external_multiple_structure(
 729                          new external_value(PARAM_RAW, 'the value to match'))
 730              )
 731          );
 732      }
 733  
 734      /**
 735       * Get user information for a unique field.
 736       *
 737       * @throws coding_exception
 738       * @throws invalid_parameter_exception
 739       * @param string $field
 740       * @param array $values
 741       * @return array An array of arrays containg user profiles.
 742       * @since Moodle 2.4
 743       */
 744      public static function get_users_by_field($field, $values) {
 745          global $CFG, $USER, $DB;
 746          require_once($CFG->dirroot . "/user/lib.php");
 747  
 748          $params = self::validate_parameters(self::get_users_by_field_parameters(),
 749                  array('field' => $field, 'values' => $values));
 750  
 751          // This array will keep all the users that are allowed to be searched,
 752          // according to the current user's privileges.
 753          $cleanedvalues = array();
 754  
 755          switch ($field) {
 756              case 'id':
 757                  $paramtype = core_user::get_property_type('id');
 758                  break;
 759              case 'idnumber':
 760                  $paramtype = core_user::get_property_type('idnumber');
 761                  break;
 762              case 'username':
 763                  $paramtype = core_user::get_property_type('username');
 764                  break;
 765              case 'email':
 766                  $paramtype = core_user::get_property_type('email');
 767                  break;
 768              default:
 769                  throw new coding_exception('invalid field parameter',
 770                          'The search field \'' . $field . '\' is not supported, look at the web service documentation');
 771          }
 772  
 773          // Clean the values.
 774          foreach ($values as $value) {
 775              $cleanedvalue = clean_param($value, $paramtype);
 776              if ( $value != $cleanedvalue) {
 777                  throw new invalid_parameter_exception('The field \'' . $field .
 778                          '\' value is invalid: ' . $value . '(cleaned value: '.$cleanedvalue.')');
 779              }
 780              $cleanedvalues[] = $cleanedvalue;
 781          }
 782  
 783          // Retrieve the users.
 784          $users = $DB->get_records_list('user', $field, $cleanedvalues, 'id');
 785  
 786          $context = context_system::instance();
 787          self::validate_context($context);
 788  
 789          // Finally retrieve each users information.
 790          $returnedusers = array();
 791          foreach ($users as $user) {
 792              $userdetails = user_get_user_details_courses($user);
 793  
 794              // Return the user only if the searched field is returned.
 795              // Otherwise it means that the $USER was not allowed to search the returned user.
 796              if (!empty($userdetails) and !empty($userdetails[$field])) {
 797                  $returnedusers[] = $userdetails;
 798              }
 799          }
 800  
 801          return $returnedusers;
 802      }
 803  
 804      /**
 805       * Returns description of method result value
 806       *
 807       * @return external_multiple_structure
 808       * @since Moodle 2.4
 809       */
 810      public static function get_users_by_field_returns() {
 811          return new external_multiple_structure(self::user_description());
 812      }
 813  
 814  
 815      /**
 816       * Returns description of get_users() parameters.
 817       *
 818       * @return external_function_parameters
 819       * @since Moodle 2.5
 820       */
 821      public static function get_users_parameters() {
 822          return new external_function_parameters(
 823              array(
 824                  'criteria' => new external_multiple_structure(
 825                      new external_single_structure(
 826                          array(
 827                              'key' => new external_value(PARAM_ALPHA, 'the user column to search, expected keys (value format) are:
 828                                  "id" (int) matching user id,
 829                                  "lastname" (string) user last name (Note: you can use % for searching but it may be considerably slower!),
 830                                  "firstname" (string) user first name (Note: you can use % for searching but it may be considerably slower!),
 831                                  "idnumber" (string) matching user idnumber,
 832                                  "username" (string) matching user username,
 833                                  "email" (string) user email (Note: you can use % for searching but it may be considerably slower!),
 834                                  "auth" (string) matching user auth plugin'),
 835                              'value' => new external_value(PARAM_RAW, 'the value to search')
 836                          )
 837                      ), 'the key/value pairs to be considered in user search. Values can not be empty.
 838                          Specify different keys only once (fullname => \'user1\', auth => \'manual\', ...) -
 839                          key occurences are forbidden.
 840                          The search is executed with AND operator on the criterias. Invalid criterias (keys) are ignored,
 841                          the search is still executed on the valid criterias.
 842                          You can search without criteria, but the function is not designed for it.
 843                          It could very slow or timeout. The function is designed to search some specific users.'
 844                  )
 845              )
 846          );
 847      }
 848  
 849      /**
 850       * Retrieve matching user.
 851       *
 852       * @throws moodle_exception
 853       * @param array $criteria the allowed array keys are id/lastname/firstname/idnumber/username/email/auth.
 854       * @return array An array of arrays containing user profiles.
 855       * @since Moodle 2.5
 856       */
 857      public static function get_users($criteria = array()) {
 858          global $CFG, $USER, $DB;
 859  
 860          require_once($CFG->dirroot . "/user/lib.php");
 861  
 862          $params = self::validate_parameters(self::get_users_parameters(),
 863                  array('criteria' => $criteria));
 864  
 865          // Validate the criteria and retrieve the users.
 866          $users = array();
 867          $warnings = array();
 868          $sqlparams = array();
 869          $usedkeys = array();
 870  
 871          // Do not retrieve deleted users.
 872          $sql = ' deleted = 0';
 873  
 874          foreach ($params['criteria'] as $criteriaindex => $criteria) {
 875  
 876              // Check that the criteria has never been used.
 877              if (array_key_exists($criteria['key'], $usedkeys)) {
 878                  throw new moodle_exception('keyalreadyset', '', '', null, 'The key ' . $criteria['key'] . ' can only be sent once');
 879              } else {
 880                  $usedkeys[$criteria['key']] = true;
 881              }
 882  
 883              $invalidcriteria = false;
 884              // Clean the parameters.
 885              $paramtype = PARAM_RAW;
 886              switch ($criteria['key']) {
 887                  case 'id':
 888                      $paramtype = core_user::get_property_type('id');
 889                      break;
 890                  case 'idnumber':
 891                      $paramtype = core_user::get_property_type('idnumber');
 892                      break;
 893                  case 'username':
 894                      $paramtype = core_user::get_property_type('username');
 895                      break;
 896                  case 'email':
 897                      // We use PARAM_RAW to allow searches with %.
 898                      $paramtype = core_user::get_property_type('email');
 899                      break;
 900                  case 'auth':
 901                      $paramtype = core_user::get_property_type('auth');
 902                      break;
 903                  case 'lastname':
 904                  case 'firstname':
 905                      $paramtype = core_user::get_property_type('firstname');
 906                      break;
 907                  default:
 908                      // Send back a warning that this search key is not supported in this version.
 909                      // This warning will make the function extandable without breaking clients.
 910                      $warnings[] = array(
 911                          'item' => $criteria['key'],
 912                          'warningcode' => 'invalidfieldparameter',
 913                          'message' =>
 914                              'The search key \'' . $criteria['key'] . '\' is not supported, look at the web service documentation'
 915                      );
 916                      // Do not add this invalid criteria to the created SQL request.
 917                      $invalidcriteria = true;
 918                      unset($params['criteria'][$criteriaindex]);
 919                      break;
 920              }
 921  
 922              if (!$invalidcriteria) {
 923                  $cleanedvalue = clean_param($criteria['value'], $paramtype);
 924  
 925                  $sql .= ' AND ';
 926  
 927                  // Create the SQL.
 928                  switch ($criteria['key']) {
 929                      case 'id':
 930                      case 'idnumber':
 931                      case 'username':
 932                      case 'auth':
 933                          $sql .= $criteria['key'] . ' = :' . $criteria['key'];
 934                          $sqlparams[$criteria['key']] = $cleanedvalue;
 935                          break;
 936                      case 'email':
 937                      case 'lastname':
 938                      case 'firstname':
 939                          $sql .= $DB->sql_like($criteria['key'], ':' . $criteria['key'], false);
 940                          $sqlparams[$criteria['key']] = $cleanedvalue;
 941                          break;
 942                      default:
 943                          break;
 944                  }
 945              }
 946          }
 947  
 948          $users = $DB->get_records_select('user', $sql, $sqlparams, 'id ASC');
 949  
 950          // Finally retrieve each users information.
 951          $returnedusers = array();
 952          foreach ($users as $user) {
 953              $userdetails = user_get_user_details_courses($user);
 954  
 955              // Return the user only if all the searched fields are returned.
 956              // Otherwise it means that the $USER was not allowed to search the returned user.
 957              if (!empty($userdetails)) {
 958                  $validuser = true;
 959  
 960                  foreach ($params['criteria'] as $criteria) {
 961                      if (empty($userdetails[$criteria['key']])) {
 962                          $validuser = false;
 963                      }
 964                  }
 965  
 966                  if ($validuser) {
 967                      $returnedusers[] = $userdetails;
 968                  }
 969              }
 970          }
 971  
 972          return array('users' => $returnedusers, 'warnings' => $warnings);
 973      }
 974  
 975      /**
 976       * Returns description of get_users result value.
 977       *
 978       * @return external_description
 979       * @since Moodle 2.5
 980       */
 981      public static function get_users_returns() {
 982          return new external_single_structure(
 983              array('users' => new external_multiple_structure(
 984                                  self::user_description()
 985                               ),
 986                    'warnings' => new external_warnings('always set to \'key\'', 'faulty key name')
 987              )
 988          );
 989      }
 990  
 991      /**
 992       * Returns description of method parameters
 993       *
 994       * @return external_function_parameters
 995       * @since Moodle 2.2
 996       */
 997      public static function get_course_user_profiles_parameters() {
 998          return new external_function_parameters(
 999              array(
1000                  'userlist' => new external_multiple_structure(
1001                      new external_single_structure(
1002                          array(
1003                              'userid'    => new external_value(core_user::get_property_type('id'), 'userid'),
1004                              'courseid'    => new external_value(PARAM_INT, 'courseid'),
1005                          )
1006                      )
1007                  )
1008              )
1009          );
1010      }
1011  
1012      /**
1013       * Get course participant's details
1014       *
1015       * @param array $userlist  array of user ids and according course ids
1016       * @return array An array of arrays describing course participants
1017       * @since Moodle 2.2
1018       */
1019      public static function get_course_user_profiles($userlist) {
1020          global $CFG, $USER, $DB;
1021          require_once($CFG->dirroot . "/user/lib.php");
1022          $params = self::validate_parameters(self::get_course_user_profiles_parameters(), array('userlist' => $userlist));
1023  
1024          $userids = array();
1025          $courseids = array();
1026          foreach ($params['userlist'] as $value) {
1027              $userids[] = $value['userid'];
1028              $courseids[$value['userid']] = $value['courseid'];
1029          }
1030  
1031          // Cache all courses.
1032          $courses = array();
1033          list($sqlcourseids, $params) = $DB->get_in_or_equal(array_unique($courseids), SQL_PARAMS_NAMED);
1034          $cselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1035          $cjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
1036          $params['contextlevel'] = CONTEXT_COURSE;
1037          $coursesql = "SELECT c.* $cselect
1038                          FROM {course} c $cjoin
1039                         WHERE c.id $sqlcourseids";
1040          $rs = $DB->get_recordset_sql($coursesql, $params);
1041          foreach ($rs as $course) {
1042              // Adding course contexts to cache.
1043              context_helper::preload_from_record($course);
1044              // Cache courses.
1045              $courses[$course->id] = $course;
1046          }
1047          $rs->close();
1048  
1049          list($sqluserids, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
1050          $uselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
1051          $ujoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
1052          $params['contextlevel'] = CONTEXT_USER;
1053          $usersql = "SELECT u.* $uselect
1054                        FROM {user} u $ujoin
1055                       WHERE u.id $sqluserids";
1056          $users = $DB->get_recordset_sql($usersql, $params);
1057          $result = array();
1058          foreach ($users as $user) {
1059              if (!empty($user->deleted)) {
1060                  continue;
1061              }
1062              context_helper::preload_from_record($user);
1063              $course = $courses[$courseids[$user->id]];
1064              $context = context_course::instance($courseids[$user->id], IGNORE_MISSING);
1065              self::validate_context($context);
1066              if ($userarray = user_get_user_details($user, $course)) {
1067                  $result[] = $userarray;
1068              }
1069          }
1070  
1071          $users->close();
1072  
1073          return $result;
1074      }
1075  
1076      /**
1077       * Returns description of method result value
1078       *
1079       * @return external_description
1080       * @since Moodle 2.2
1081       */
1082      public static function get_course_user_profiles_returns() {
1083          $additionalfields = array(
1084              'groups' => new external_multiple_structure(
1085                  new external_single_structure(
1086                      array(
1087                          'id'  => new external_value(PARAM_INT, 'group id'),
1088                          'name' => new external_value(PARAM_RAW, 'group name'),
1089                          'description' => new external_value(PARAM_RAW, 'group description'),
1090                          'descriptionformat' => new external_format_value('description'),
1091                      )
1092                  ), 'user groups', VALUE_OPTIONAL),
1093              'roles' => new external_multiple_structure(
1094                  new external_single_structure(
1095                      array(
1096                          'roleid'       => new external_value(PARAM_INT, 'role id'),
1097                          'name'         => new external_value(PARAM_RAW, 'role name'),
1098                          'shortname'    => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
1099                          'sortorder'    => new external_value(PARAM_INT, 'role sortorder')
1100                      )
1101                  ), 'user roles', VALUE_OPTIONAL),
1102              'enrolledcourses' => new external_multiple_structure(
1103                  new external_single_structure(
1104                      array(
1105                          'id'  => new external_value(PARAM_INT, 'Id of the course'),
1106                          'fullname'  => new external_value(PARAM_RAW, 'Fullname of the course'),
1107                          'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
1108                      )
1109                  ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
1110          );
1111  
1112          return new external_multiple_structure(self::user_description($additionalfields));
1113      }
1114  
1115      /**
1116       * Create user return value description.
1117       *
1118       * @param array $additionalfields some additional field
1119       * @return single_structure_description
1120       */
1121      public static function user_description($additionalfields = array()) {
1122          $userfields = array(
1123              'id'    => new external_value(core_user::get_property_type('id'), 'ID of the user'),
1124              'username'    => new external_value(core_user::get_property_type('username'), 'The username', VALUE_OPTIONAL),
1125              'firstname'   => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user', VALUE_OPTIONAL),
1126              'lastname'    => new external_value(core_user::get_property_type('lastname'), 'The family name of the user', VALUE_OPTIONAL),
1127              'fullname'    => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'),
1128              'email'       => new external_value(core_user::get_property_type('email'), 'An email address - allow email as root@localhost', VALUE_OPTIONAL),
1129              'address'     => new external_value(core_user::get_property_type('address'), 'Postal address', VALUE_OPTIONAL),
1130              'phone1'      => new external_value(core_user::get_property_type('phone1'), 'Phone 1', VALUE_OPTIONAL),
1131              'phone2'      => new external_value(core_user::get_property_type('phone2'), 'Phone 2', VALUE_OPTIONAL),
1132              'department'  => new external_value(core_user::get_property_type('department'), 'department', VALUE_OPTIONAL),
1133              'institution' => new external_value(core_user::get_property_type('institution'), 'institution', VALUE_OPTIONAL),
1134              'idnumber'    => new external_value(core_user::get_property_type('idnumber'), 'An arbitrary ID code number perhaps from the institution', VALUE_OPTIONAL),
1135              'interests'   => new external_value(PARAM_TEXT, 'user interests (separated by commas)', VALUE_OPTIONAL),
1136              'firstaccess' => new external_value(core_user::get_property_type('firstaccess'), 'first access to the site (0 if never)', VALUE_OPTIONAL),
1137              'lastaccess'  => new external_value(core_user::get_property_type('lastaccess'), 'last access to the site (0 if never)', VALUE_OPTIONAL),
1138              'auth'        => new external_value(core_user::get_property_type('auth'), 'Auth plugins include manual, ldap, etc', VALUE_OPTIONAL),
1139              'suspended'   => new external_value(core_user::get_property_type('suspended'), 'Suspend user account, either false to enable user login or true to disable it', VALUE_OPTIONAL),
1140              'confirmed'   => new external_value(core_user::get_property_type('confirmed'), 'Active user: 1 if confirmed, 0 otherwise', VALUE_OPTIONAL),
1141              'lang'        => new external_value(core_user::get_property_type('lang'), 'Language code such as "en", must exist on server', VALUE_OPTIONAL),
1142              'calendartype' => new external_value(core_user::get_property_type('calendartype'), 'Calendar type such as "gregorian", must exist on server', VALUE_OPTIONAL),
1143              'theme'       => new external_value(core_user::get_property_type('theme'), 'Theme name such as "standard", must exist on server', VALUE_OPTIONAL),
1144              'timezone'    => new external_value(core_user::get_property_type('timezone'), 'Timezone code such as Australia/Perth, or 99 for default', VALUE_OPTIONAL),
1145              'mailformat'  => new external_value(core_user::get_property_type('mailformat'), 'Mail format code is 0 for plain text, 1 for HTML etc', VALUE_OPTIONAL),
1146              'description' => new external_value(core_user::get_property_type('description'), 'User profile description', VALUE_OPTIONAL),
1147              'descriptionformat' => new external_format_value(core_user::get_property_type('descriptionformat'), VALUE_OPTIONAL),
1148              'city'        => new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_OPTIONAL),
1149              'country'     => new external_value(core_user::get_property_type('country'), 'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
1150              'profileimageurlsmall' => new external_value(PARAM_URL, 'User image profile URL - small version'),
1151              'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big version'),
1152              'customfields' => new external_multiple_structure(
1153                  new external_single_structure(
1154                      array(
1155                          'type'  => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field - text field, checkbox...'),
1156                          'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
1157                          'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
1158                          'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field - to be able to build the field class in the code'),
1159                      )
1160                  ), 'User custom fields (also known as user profile fields)', VALUE_OPTIONAL),
1161              'preferences' => new external_multiple_structure(
1162                  new external_single_structure(
1163                      array(
1164                          'name'  => new external_value(PARAM_RAW, 'The name of the preferences'),
1165                          'value' => new external_value(PARAM_RAW, 'The value of the preference'),
1166                      )
1167              ), 'Users preferences', VALUE_OPTIONAL)
1168          );
1169          if (!empty($additionalfields)) {
1170              $userfields = array_merge($userfields, $additionalfields);
1171          }
1172          return new external_single_structure($userfields);
1173      }
1174  
1175      /**
1176       * Returns description of method parameters
1177       *
1178       * @return external_function_parameters
1179       * @since Moodle 2.6
1180       */
1181      public static function add_user_private_files_parameters() {
1182          return new external_function_parameters(
1183              array(
1184                  'draftid' => new external_value(PARAM_INT, 'draft area id')
1185              )
1186          );
1187      }
1188  
1189      /**
1190       * Copy files from a draft area to users private files area.
1191       *
1192       * @throws invalid_parameter_exception
1193       * @throws moodle_exception
1194       * @param int $draftid Id of a draft area containing files.
1195       * @return array An array of warnings
1196       * @since Moodle 2.6
1197       */
1198      public static function add_user_private_files($draftid) {
1199          global $CFG, $USER;
1200          require_once($CFG->libdir . "/filelib.php");
1201  
1202          $params = self::validate_parameters(self::add_user_private_files_parameters(), array('draftid' => $draftid));
1203  
1204          if (isguestuser()) {
1205              throw new invalid_parameter_exception('Guest users cannot upload files');
1206          }
1207  
1208          $context = context_user::instance($USER->id);
1209          require_capability('moodle/user:manageownfiles', $context);
1210  
1211          $maxbytes = $CFG->userquota;
1212          $maxareabytes = $CFG->userquota;
1213          if (has_capability('moodle/user:ignoreuserquota', $context)) {
1214              $maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS;
1215              $maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED;
1216          } else {
1217              // Get current used space for this user (private files only).
1218              $fileareainfo = file_get_file_area_info($context->id, 'user', 'private');
1219              $usedspace = $fileareainfo['filesize_without_references'];
1220  
1221              // Get the total size of the new files we want to add to private files.
1222              $newfilesinfo = file_get_draft_area_info($params['draftid']);
1223  
1224              if (($newfilesinfo['filesize_without_references'] + $usedspace) > $maxareabytes) {
1225                  throw new moodle_exception('maxareabytes');
1226              }
1227          }
1228  
1229          $options = array('subdirs' => 1,
1230                           'maxbytes' => $maxbytes,
1231                           'maxfiles' => -1,
1232                           'areamaxbytes' => $maxareabytes);
1233  
1234          file_merge_files_from_draft_area_into_filearea($draftid, $context->id, 'user', 'private', 0, $options);
1235  
1236          return null;
1237      }
1238  
1239      /**
1240       * Returns description of method result value
1241       *
1242       * @return external_description
1243       * @since Moodle 2.2
1244       */
1245      public static function add_user_private_files_returns() {
1246          return null;
1247      }
1248  
1249      /**
1250       * Returns description of method parameters.
1251       *
1252       * @return external_function_parameters
1253       * @since Moodle 2.6
1254       */
1255      public static function add_user_device_parameters() {
1256          return new external_function_parameters(
1257              array(
1258                  'appid'     => new external_value(PARAM_NOTAGS, 'the app id, usually something like com.moodle.moodlemobile'),
1259                  'name'      => new external_value(PARAM_NOTAGS, 'the device name, \'occam\' or \'iPhone\' etc.'),
1260                  'model'     => new external_value(PARAM_NOTAGS, 'the device model \'Nexus4\' or \'iPad1,1\' etc.'),
1261                  'platform'  => new external_value(PARAM_NOTAGS, 'the device platform \'iOS\' or \'Android\' etc.'),
1262                  'version'   => new external_value(PARAM_NOTAGS, 'the device version \'6.1.2\' or \'4.2.2\' etc.'),
1263                  'pushid'    => new external_value(PARAM_RAW, 'the device PUSH token/key/identifier/registration id'),
1264                  'uuid'      => new external_value(PARAM_RAW, 'the device UUID'),
1265                  'publickey' => new external_value(PARAM_RAW, 'the app generated public key', VALUE_DEFAULT, null),
1266              )
1267          );
1268      }
1269  
1270      /**
1271       * Add a user device in Moodle database (for PUSH notifications usually).
1272       *
1273       * @throws moodle_exception
1274       * @param string $appid The app id, usually something like com.moodle.moodlemobile.
1275       * @param string $name The device name, occam or iPhone etc.
1276       * @param string $model The device model Nexus4 or iPad1.1 etc.
1277       * @param string $platform The device platform iOs or Android etc.
1278       * @param string $version The device version 6.1.2 or 4.2.2 etc.
1279       * @param string $pushid The device PUSH token/key/identifier/registration id.
1280       * @param string $uuid The device UUID.
1281       * @param string $publickey The app generated public key
1282       * @return array List of possible warnings.
1283       * @since Moodle 2.6
1284       */
1285      public static function add_user_device($appid, $name, $model, $platform, $version, $pushid, $uuid, $publickey = null) {
1286          global $CFG, $USER, $DB;
1287          require_once($CFG->dirroot . "/user/lib.php");
1288  
1289          $params = self::validate_parameters(self::add_user_device_parameters(),
1290                  array('appid' => $appid,
1291                        'name' => $name,
1292                        'model' => $model,
1293                        'platform' => $platform,
1294                        'version' => $version,
1295                        'pushid' => $pushid,
1296                        'uuid' => $uuid,
1297                        'publickey' => $publickey,
1298                        ));
1299  
1300          $warnings = array();
1301  
1302          // Prevent duplicate keys for users.
1303          if ($DB->get_record('user_devices', array('pushid' => $params['pushid'], 'userid' => $USER->id))) {
1304              $warnings['warning'][] = array(
1305                  'item' => $params['pushid'],
1306                  'warningcode' => 'existingkeyforthisuser',
1307                  'message' => 'This key is already stored for this user'
1308              );
1309              return $warnings;
1310          }
1311  
1312          // Notice that we can have multiple devices because previously it was allowed to have repeated ones.
1313          // Since we don't have a clear way to decide which one is the more appropiate, we update all.
1314          if ($userdevices = $DB->get_records('user_devices', array('uuid' => $params['uuid'],
1315                  'appid' => $params['appid'], 'userid' => $USER->id))) {
1316  
1317              foreach ($userdevices as $userdevice) {
1318                  $userdevice->version    = $params['version'];   // Maybe the user upgraded the device.
1319                  $userdevice->pushid     = $params['pushid'];
1320                  $userdevice->publickey  = $params['publickey'];
1321                  $userdevice->timemodified  = time();
1322                  $DB->update_record('user_devices', $userdevice);
1323              }
1324  
1325          } else {
1326              $userdevice = new stdclass;
1327              $userdevice->userid     = $USER->id;
1328              $userdevice->appid      = $params['appid'];
1329              $userdevice->name       = $params['name'];
1330              $userdevice->model      = $params['model'];
1331              $userdevice->platform   = $params['platform'];
1332              $userdevice->version    = $params['version'];
1333              $userdevice->pushid     = $params['pushid'];
1334              $userdevice->uuid       = $params['uuid'];
1335              $userdevice->publickey  = $params['publickey'];
1336              $userdevice->timecreated  = time();
1337              $userdevice->timemodified = $userdevice->timecreated;
1338  
1339              if (!$DB->insert_record('user_devices', $userdevice)) {
1340                  throw new moodle_exception("There was a problem saving in the database the device with key: " . $params['pushid']);
1341              }
1342          }
1343  
1344          return $warnings;
1345      }
1346  
1347      /**
1348       * Returns description of method result value.
1349       *
1350       * @return external_multiple_structure
1351       * @since Moodle 2.6
1352       */
1353      public static function add_user_device_returns() {
1354          return new external_multiple_structure(
1355             new external_warnings()
1356          );
1357      }
1358  
1359      /**
1360       * Returns description of method parameters.
1361       *
1362       * @return external_function_parameters
1363       * @since Moodle 2.9
1364       */
1365      public static function remove_user_device_parameters() {
1366          return new external_function_parameters(
1367              array(
1368                  'uuid'  => new external_value(PARAM_RAW, 'the device UUID'),
1369                  'appid' => new external_value(PARAM_NOTAGS,
1370                                                  'the app id, if empty devices matching the UUID for the user will be removed',
1371                                                  VALUE_DEFAULT, ''),
1372              )
1373          );
1374      }
1375  
1376      /**
1377       * Remove a user device from the Moodle database (for PUSH notifications usually).
1378       *
1379       * @param string $uuid The device UUID.
1380       * @param string $appid The app id, opitonal parameter. If empty all the devices fmatching the UUID or the user will be removed.
1381       * @return array List of possible warnings and removal status.
1382       * @since Moodle 2.9
1383       */
1384      public static function remove_user_device($uuid, $appid = "") {
1385          global $CFG;
1386          require_once($CFG->dirroot . "/user/lib.php");
1387  
1388          $params = self::validate_parameters(self::remove_user_device_parameters(), array('uuid' => $uuid, 'appid' => $appid));
1389  
1390          $context = context_system::instance();
1391          self::validate_context($context);
1392  
1393          // Warnings array, it can be empty at the end but is mandatory.
1394          $warnings = array();
1395  
1396          $removed = user_remove_user_device($params['uuid'], $params['appid']);
1397  
1398          if (!$removed) {
1399              $warnings[] = array(
1400                  'item' => $params['uuid'],
1401                  'warningcode' => 'devicedoesnotexist',
1402                  'message' => 'The device doesn\'t exists in the database'
1403              );
1404          }
1405  
1406          $result = array(
1407              'removed' => $removed,
1408              'warnings' => $warnings
1409          );
1410  
1411          return $result;
1412      }
1413  
1414      /**
1415       * Returns description of method result value.
1416       *
1417       * @return external_multiple_structure
1418       * @since Moodle 2.9
1419       */
1420      public static function remove_user_device_returns() {
1421          return new external_single_structure(
1422              array(
1423                  'removed' => new external_value(PARAM_BOOL, 'True if removed, false if not removed because it doesn\'t exists'),
1424                  'warnings' => new external_warnings(),
1425              )
1426          );
1427      }
1428  
1429      /**
1430       * Returns description of method parameters
1431       *
1432       * @return external_function_parameters
1433       * @since Moodle 2.9
1434       */
1435      public static function view_user_list_parameters() {
1436          return new external_function_parameters(
1437              array(
1438                  'courseid' => new external_value(PARAM_INT, 'id of the course, 0 for site')
1439              )
1440          );
1441      }
1442  
1443      /**
1444       * Trigger the user_list_viewed event.
1445       *
1446       * @param int $courseid id of course
1447       * @return array of warnings and status result
1448       * @since Moodle 2.9
1449       * @throws moodle_exception
1450       */
1451      public static function view_user_list($courseid) {
1452          global $CFG;
1453          require_once($CFG->dirroot . "/user/lib.php");
1454          require_once($CFG->dirroot . '/course/lib.php');
1455  
1456          $params = self::validate_parameters(self::view_user_list_parameters(),
1457                                              array(
1458                                                  'courseid' => $courseid
1459                                              ));
1460  
1461          $warnings = array();
1462  
1463          if (empty($params['courseid'])) {
1464              $params['courseid'] = SITEID;
1465          }
1466  
1467          $course = get_course($params['courseid']);
1468  
1469          if ($course->id == SITEID) {
1470              $context = context_system::instance();
1471          } else {
1472              $context = context_course::instance($course->id);
1473          }
1474          self::validate_context($context);
1475  
1476          course_require_view_participants($context);
1477  
1478          user_list_view($course, $context);
1479  
1480          $result = array();
1481          $result['status'] = true;
1482          $result['warnings'] = $warnings;
1483          return $result;
1484      }
1485  
1486      /**
1487       * Returns description of method result value
1488       *
1489       * @return external_description
1490       * @since Moodle 2.9
1491       */
1492      public static function view_user_list_returns() {
1493          return new external_single_structure(
1494              array(
1495                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1496                  'warnings' => new external_warnings()
1497              )
1498          );
1499      }
1500  
1501      /**
1502       * Returns description of method parameters
1503       *
1504       * @return external_function_parameters
1505       * @since Moodle 2.9
1506       */
1507      public static function view_user_profile_parameters() {
1508          return new external_function_parameters(
1509              array(
1510                  'userid' => new external_value(PARAM_INT, 'id of the user, 0 for current user', VALUE_REQUIRED),
1511                  'courseid' => new external_value(PARAM_INT, 'id of the course, default site course', VALUE_DEFAULT, 0)
1512              )
1513          );
1514      }
1515  
1516      /**
1517       * Trigger the user profile viewed event.
1518       *
1519       * @param int $userid id of user
1520       * @param int $courseid id of course
1521       * @return array of warnings and status result
1522       * @since Moodle 2.9
1523       * @throws moodle_exception
1524       */
1525      public static function view_user_profile($userid, $courseid = 0) {
1526          global $CFG, $USER;
1527          require_once($CFG->dirroot . "/user/profile/lib.php");
1528  
1529          $params = self::validate_parameters(self::view_user_profile_parameters(),
1530                                              array(
1531                                                  'userid' => $userid,
1532                                                  'courseid' => $courseid
1533                                              ));
1534  
1535          $warnings = array();
1536  
1537          if (empty($params['userid'])) {
1538              $params['userid'] = $USER->id;
1539          }
1540  
1541          if (empty($params['courseid'])) {
1542              $params['courseid'] = SITEID;
1543          }
1544  
1545          $course = get_course($params['courseid']);
1546          $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
1547          core_user::require_active_user($user);
1548  
1549          if ($course->id == SITEID) {
1550              $coursecontext = context_system::instance();;
1551          } else {
1552              $coursecontext = context_course::instance($course->id);
1553          }
1554          self::validate_context($coursecontext);
1555  
1556          $currentuser = $USER->id == $user->id;
1557          $usercontext = context_user::instance($user->id);
1558  
1559          if (!$currentuser and
1560                  !has_capability('moodle/user:viewdetails', $coursecontext) and
1561                  !has_capability('moodle/user:viewdetails', $usercontext)) {
1562              throw new moodle_exception('cannotviewprofile');
1563          }
1564  
1565          // Case like user/profile.php.
1566          if ($course->id == SITEID) {
1567              profile_view($user, $usercontext);
1568          } else {
1569              // Case like user/view.php.
1570              if (!$currentuser and !can_access_course($course, $user, '', true)) {
1571                  throw new moodle_exception('notenrolledprofile');
1572              }
1573  
1574              profile_view($user, $coursecontext, $course);
1575          }
1576  
1577          $result = array();
1578          $result['status'] = true;
1579          $result['warnings'] = $warnings;
1580          return $result;
1581      }
1582  
1583      /**
1584       * Returns description of method result value
1585       *
1586       * @return external_description
1587       * @since Moodle 2.9
1588       */
1589      public static function view_user_profile_returns() {
1590          return new external_single_structure(
1591              array(
1592                  'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1593                  'warnings' => new external_warnings()
1594              )
1595          );
1596      }
1597  
1598      /**
1599       * Returns description of method parameters
1600       *
1601       * @return external_function_parameters
1602       * @since Moodle 3.2
1603       */
1604      public static function get_user_preferences_parameters() {
1605          return new external_function_parameters(
1606              array(
1607                  'name' => new external_value(PARAM_RAW, 'preference name, empty for all', VALUE_DEFAULT, ''),
1608                  'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0)
1609              )
1610          );
1611      }
1612  
1613      /**
1614       * Return user preferences.
1615       *
1616       * @param string $name preference name, empty for all
1617       * @param int $userid id of the user, 0 for current user
1618       * @return array of warnings and preferences
1619       * @since Moodle 3.2
1620       * @throws moodle_exception
1621       */
1622      public static function get_user_preferences($name = '', $userid = 0) {
1623          global $USER;
1624  
1625          $params = self::validate_parameters(self::get_user_preferences_parameters(),
1626                                              array(
1627                                                  'name' => $name,
1628                                                  'userid' => $userid
1629                                              ));
1630          $preferences = array();
1631          $warnings = array();
1632  
1633          $context = context_system::instance();
1634          self::validate_context($context);
1635  
1636          if (empty($params['name'])) {
1637              $name = null;
1638          }
1639          if (empty($params['userid'])) {
1640              $user = null;
1641          } else {
1642              $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
1643              core_user::require_active_user($user);
1644              if ($user->id != $USER->id) {
1645                  // Only admins can retrieve other users preferences.
1646                  require_capability('moodle/site:config', $context);
1647              }
1648          }
1649  
1650          $userpreferences = get_user_preferences($name, null, $user);
1651          // Check if we received just one preference.
1652          if (!is_array($userpreferences)) {
1653              $userpreferences = array($name => $userpreferences);
1654          }
1655  
1656          foreach ($userpreferences as $name => $value) {
1657              $preferences[] = array(
1658                  'name' => $name,
1659                  'value' => $value,
1660              );
1661          }
1662  
1663          $result = array();
1664          $result['preferences'] = $preferences;
1665          $result['warnings'] = $warnings;
1666          return $result;
1667      }
1668  
1669      /**
1670       * Returns description of method result value
1671       *
1672       * @return external_description
1673       * @since Moodle 3.2
1674       */
1675      public static function get_user_preferences_returns() {
1676          return new external_single_structure(
1677              array(
1678                  'preferences' => new external_multiple_structure(
1679                      new external_single_structure(
1680                          array(
1681                              'name' => new external_value(PARAM_RAW, 'The name of the preference'),
1682                              'value' => new external_value(PARAM_RAW, 'The value of the preference'),
1683                          )
1684                      ),
1685                      'User custom fields (also known as user profile fields)'
1686                  ),
1687                  'warnings' => new external_warnings()
1688              )
1689          );
1690      }
1691  
1692      /**
1693       * Returns description of method parameters
1694       *
1695       * @return external_function_parameters
1696       * @since Moodle 3.2
1697       */
1698      public static function update_picture_parameters() {
1699          return new external_function_parameters(
1700              array(
1701                  'draftitemid' => new external_value(PARAM_INT, 'Id of the user draft file to use as image'),
1702                  'delete' => new external_value(PARAM_BOOL, 'If we should delete the user picture', VALUE_DEFAULT, false),
1703                  'userid' => new external_value(PARAM_INT, 'Id of the user, 0 for current user', VALUE_DEFAULT, 0)
1704              )
1705          );
1706      }
1707  
1708      /**
1709       * Update or delete the user picture in the site
1710       *
1711       * @param  int  $draftitemid id of the user draft file to use as image
1712       * @param  bool $delete      if we should delete the user picture
1713       * @param  int $userid       id of the user, 0 for current user
1714       * @return array warnings and success status
1715       * @since Moodle 3.2
1716       * @throws moodle_exception
1717       */
1718      public static function update_picture($draftitemid, $delete = false, $userid = 0) {
1719          global $CFG, $USER, $PAGE;
1720  
1721          $params = self::validate_parameters(
1722              self::update_picture_parameters(),
1723              array(
1724                  'draftitemid' => $draftitemid,
1725                  'delete' => $delete,
1726                  'userid' => $userid
1727              )
1728          );
1729  
1730          $context = context_system::instance();
1731          self::validate_context($context);
1732  
1733          if (!empty($CFG->disableuserimages)) {
1734              throw new moodle_exception('userimagesdisabled', 'admin');
1735          }
1736  
1737          if (empty($params['userid']) or $params['userid'] == $USER->id) {
1738              $user = $USER;
1739              require_capability('moodle/user:editownprofile', $context);
1740          } else {
1741              $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
1742              core_user::require_active_user($user);
1743              $personalcontext = context_user::instance($user->id);
1744  
1745              require_capability('moodle/user:editprofile', $personalcontext);
1746              if (is_siteadmin($user) and !is_siteadmin($USER)) {  // Only admins may edit other admins.
1747                  throw new moodle_exception('useradmineditadmin');
1748              }
1749          }
1750  
1751          // Load the appropriate auth plugin.
1752          $userauth = get_auth_plugin($user->auth);
1753          if (is_mnet_remote_user($user) or !$userauth->can_edit_profile() or $userauth->edit_profile_url()) {
1754              throw new moodle_exception('noprofileedit', 'auth');
1755          }
1756  
1757          $filemanageroptions = array(
1758              'maxbytes' => $CFG->maxbytes,
1759              'subdirs' => 0,
1760              'maxfiles' => 1,
1761              'accepted_types' => 'optimised_image'
1762          );
1763          $user->deletepicture = $params['delete'];
1764          $user->imagefile = $params['draftitemid'];
1765          $success = core_user::update_picture($user, $filemanageroptions);
1766  
1767          $result = array(
1768              'success' => $success,
1769              'warnings' => array(),
1770          );
1771          if ($success) {
1772              $userpicture = new user_picture(core_user::get_user($user->id));
1773              $userpicture->size = 1; // Size f1.
1774              $result['profileimageurl'] = $userpicture->get_url($PAGE)->out(false);
1775          }
1776          return $result;
1777      }
1778  
1779      /**
1780       * Returns description of method result value
1781       *
1782       * @return external_description
1783       * @since Moodle 3.2
1784       */
1785      public static function update_picture_returns() {
1786          return new external_single_structure(
1787              array(
1788                  'success' => new external_value(PARAM_BOOL, 'True if the image was updated, false otherwise.'),
1789                  'profileimageurl' => new external_value(PARAM_URL, 'New profile user image url', VALUE_OPTIONAL),
1790                  'warnings' => new external_warnings()
1791              )
1792          );
1793      }
1794  
1795      /**
1796       * Returns description of method parameters
1797       *
1798       * @return external_function_parameters
1799       * @since Moodle 3.2
1800       */
1801      public static function set_user_preferences_parameters() {
1802          return new external_function_parameters(
1803              array(
1804                  'preferences' => new external_multiple_structure(
1805                      new external_single_structure(
1806                          array(
1807                              'name' => new external_value(PARAM_RAW, 'The name of the preference'),
1808                              'value' => new external_value(PARAM_RAW, 'The value of the preference'),
1809                              'userid' => new external_value(PARAM_INT, 'Id of the user to set the preference'),
1810                          )
1811                      )
1812                  )
1813              )
1814          );
1815      }
1816  
1817      /**
1818       * Set user preferences.
1819       *
1820       * @param array $preferences list of preferences including name, value and userid
1821       * @return array of warnings and preferences saved
1822       * @since Moodle 3.2
1823       * @throws moodle_exception
1824       */
1825      public static function set_user_preferences($preferences) {
1826          global $USER;
1827  
1828          $params = self::validate_parameters(self::set_user_preferences_parameters(), array('preferences' => $preferences));
1829          $warnings = array();
1830          $saved = array();
1831  
1832          $context = context_system::instance();
1833          self::validate_context($context);
1834  
1835          $userscache = array();
1836          foreach ($params['preferences'] as $pref) {
1837              // Check to which user set the preference.
1838              if (!empty($userscache[$pref['userid']])) {
1839                  $user = $userscache[$pref['userid']];
1840              } else {
1841                  try {
1842                      $user = core_user::get_user($pref['userid'], '*', MUST_EXIST);
1843                      core_user::require_active_user($user);
1844                      $userscache[$pref['userid']] = $user;
1845                  } catch (Exception $e) {
1846                      $warnings[] = array(
1847                          'item' => 'user',
1848                          'itemid' => $pref['userid'],
1849                          'warningcode' => 'invaliduser',
1850                          'message' => $e->getMessage()
1851                      );
1852                      continue;
1853                  }
1854              }
1855  
1856              try {
1857                  if (core_user::can_edit_preference($pref['name'], $user)) {
1858                      $value = core_user::clean_preference($pref['value'], $pref['name']);
1859                      set_user_preference($pref['name'], $value, $user->id);
1860                      $saved[] = array(
1861                          'name' => $pref['name'],
1862                          'userid' => $user->id,
1863                      );
1864                  } else {
1865                      $warnings[] = array(
1866                          'item' => 'user',
1867                          'itemid' => $user->id,
1868                          'warningcode' => 'nopermission',
1869                          'message' => 'You are not allowed to change the preference '.s($pref['name']).' for user '.$user->id
1870                      );
1871                  }
1872              } catch (Exception $e) {
1873                  $warnings[] = array(
1874                      'item' => 'user',
1875                      'itemid' => $user->id,
1876                      'warningcode' => 'errorsavingpreference',
1877                      'message' => $e->getMessage()
1878                  );
1879              }
1880          }
1881  
1882          $result = array();
1883          $result['saved'] = $saved;
1884          $result['warnings'] = $warnings;
1885          return $result;
1886      }
1887  
1888      /**
1889       * Returns description of method result value
1890       *
1891       * @return external_description
1892       * @since Moodle 3.2
1893       */
1894      public static function set_user_preferences_returns() {
1895          return new external_single_structure(
1896              array(
1897                  'saved' => new external_multiple_structure(
1898                      new external_single_structure(
1899                          array(
1900                              'name' => new external_value(PARAM_RAW, 'The name of the preference'),
1901                              'userid' => new external_value(PARAM_INT, 'The user the preference was set for'),
1902                          )
1903                      ), 'Preferences saved'
1904                  ),
1905                  'warnings' => new external_warnings()
1906              )
1907          );
1908      }
1909  
1910      /**
1911       * Returns description of method parameters.
1912       *
1913       * @return external_function_parameters
1914       * @since Moodle 3.2
1915       */
1916      public static function agree_site_policy_parameters() {
1917          return new external_function_parameters(array());
1918      }
1919  
1920      /**
1921       * Agree the site policy for the current user.
1922       *
1923       * @return array of warnings and status result
1924       * @since Moodle 3.2
1925       * @throws moodle_exception
1926       */
1927      public static function agree_site_policy() {
1928          global $CFG, $DB, $USER;
1929  
1930          $warnings = array();
1931  
1932          $context = context_system::instance();
1933          try {
1934              // We expect an exception here since the user didn't agree the site policy yet.
1935              self::validate_context($context);
1936          } catch (Exception $e) {
1937              // We are expecting only a sitepolicynotagreed exception.
1938              if (!($e instanceof moodle_exception) or $e->errorcode != 'sitepolicynotagreed') {
1939                  // In case we receive a different exception, throw it.
1940                  throw $e;
1941              }
1942          }
1943  
1944          $manager = new \core_privacy\local\sitepolicy\manager();
1945          if (!empty($USER->policyagreed)) {
1946              $status = false;
1947              $warnings[] = array(
1948                  'item' => 'user',
1949                  'itemid' => $USER->id,
1950                  'warningcode' => 'alreadyagreed',
1951                  'message' => 'The user already agreed the site policy.'
1952              );
1953          } else if (!$manager->is_defined()) {
1954              $status = false;
1955              $warnings[] = array(
1956                  'item' => 'user',
1957                  'itemid' => $USER->id,
1958                  'warningcode' => 'nositepolicy',
1959                  'message' => 'The site does not have a site policy configured.'
1960              );
1961          } else {
1962              $status = $manager->accept();
1963          }
1964  
1965          $result = array();
1966          $result['status'] = $status;
1967          $result['warnings'] = $warnings;
1968          return $result;
1969      }
1970  
1971      /**
1972       * Returns description of method result value.
1973       *
1974       * @return external_description
1975       * @since Moodle 3.2
1976       */
1977      public static function agree_site_policy_returns() {
1978          return new external_single_structure(
1979              array(
1980                  'status' => new external_value(PARAM_BOOL, 'Status: true only if we set the policyagreed to 1 for the user'),
1981                  'warnings' => new external_warnings()
1982              )
1983          );
1984      }
1985  
1986      /**
1987       * Returns description of method parameters.
1988       *
1989       * @return external_function_parameters
1990       * @since Moodle 3.4
1991       */
1992      public static function get_private_files_info_parameters() {
1993          return new external_function_parameters(
1994              array(
1995                  'userid' => new external_value(PARAM_INT, 'Id of the user, default to current user.', VALUE_DEFAULT, 0)
1996              )
1997          );
1998      }
1999  
2000      /**
2001       * Returns general information about files in the user private files area.
2002       *
2003       * @param int $userid Id of the user, default to current user.
2004       * @return array of warnings and file area information
2005       * @since Moodle 3.4
2006       * @throws moodle_exception
2007       */
2008      public static function get_private_files_info($userid = 0) {
2009          global $CFG, $USER;
2010          require_once($CFG->libdir . '/filelib.php');
2011  
2012          $params = self::validate_parameters(self::get_private_files_info_parameters(), array('userid' => $userid));
2013          $warnings = array();
2014  
2015          $context = context_system::instance();
2016          self::validate_context($context);
2017  
2018          if (empty($params['userid']) || $params['userid'] == $USER->id) {
2019              $usercontext = context_user::instance($USER->id);
2020              require_capability('moodle/user:manageownfiles', $usercontext);
2021          } else {
2022              $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
2023              core_user::require_active_user($user);
2024              // Only admins can retrieve other users information.
2025              require_capability('moodle/site:config', $context);
2026              $usercontext = context_user::instance($user->id);
2027          }
2028  
2029          $fileareainfo = file_get_file_area_info($usercontext->id, 'user', 'private');
2030  
2031          $result = array();
2032          $result['filecount'] = $fileareainfo['filecount'];
2033          $result['foldercount'] = $fileareainfo['foldercount'];
2034          $result['filesize'] = $fileareainfo['filesize'];
2035          $result['filesizewithoutreferences'] = $fileareainfo['filesize_without_references'];
2036          $result['warnings'] = $warnings;
2037          return $result;
2038      }
2039  
2040      /**
2041       * Returns description of method result value.
2042       *
2043       * @return external_description
2044       * @since Moodle 3.4
2045       */
2046      public static function get_private_files_info_returns() {
2047          return new external_single_structure(
2048              array(
2049                  'filecount' => new external_value(PARAM_INT, 'Number of files in the area.'),
2050                  'foldercount' => new external_value(PARAM_INT, 'Number of folders in the area.'),
2051                  'filesize' => new external_value(PARAM_INT, 'Total size of the files in the area.'),
2052                  'filesizewithoutreferences' => new external_value(PARAM_INT, 'Total size of the area excluding file references'),
2053                  'warnings' => new external_warnings()
2054              )
2055          );
2056      }
2057  }