Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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