Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * User external PHPunit tests
  19   *
  20   * @package    core_user
  21   * @category   external
  22   * @copyright  2012 Jerome Mouneyrac
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since Moodle 2.4
  25   */
  26  
  27  namespace core_user;
  28  
  29  use core_external\external_api;
  30  use core_files_external;
  31  use core_user_external;
  32  use externallib_advanced_testcase;
  33  
  34  defined('MOODLE_INTERNAL') || die();
  35  
  36  global $CFG;
  37  
  38  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  39  require_once($CFG->dirroot . '/user/externallib.php');
  40  require_once($CFG->dirroot . '/files/externallib.php');
  41  
  42  class externallib_test extends externallib_advanced_testcase {
  43  
  44      /**
  45       * Test get_users
  46       */
  47      public function test_get_users() {
  48          global $USER, $CFG;
  49  
  50          $this->resetAfterTest(true);
  51  
  52          $course = self::getDataGenerator()->create_course();
  53  
  54          $user1 = array(
  55              'username' => 'usernametest1',
  56              'idnumber' => 'idnumbertest1',
  57              'firstname' => 'First Name User Test 1',
  58              'lastname' => 'Last Name User Test 1',
  59              'email' => 'usertest1@example.com',
  60              'address' => '2 Test Street Perth 6000 WA',
  61              'phone1' => '01010101010',
  62              'phone2' => '02020203',
  63              'department' => 'Department of user 1',
  64              'institution' => 'Institution of user 1',
  65              'description' => 'This is a description for user 1',
  66              'descriptionformat' => FORMAT_MOODLE,
  67              'city' => 'Perth',
  68              'country' => 'AU'
  69              );
  70  
  71          $user1 = self::getDataGenerator()->create_user($user1);
  72          set_config('usetags', 1);
  73          require_once($CFG->dirroot . '/user/editlib.php');
  74          $user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
  75          useredit_update_interests($user1, $user1->interests);
  76  
  77          $user2 = self::getDataGenerator()->create_user(
  78                  array('username' => 'usernametest2', 'idnumber' => 'idnumbertest2'));
  79  
  80          $generatedusers = array();
  81          $generatedusers[$user1->id] = $user1;
  82          $generatedusers[$user2->id] = $user2;
  83  
  84          $context = \context_course::instance($course->id);
  85          $roleid = $this->assignUserCapability('moodle/user:viewdetails', $context->id);
  86  
  87          // Enrol the users in the course.
  88          $this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid);
  89          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid);
  90          $this->getDataGenerator()->enrol_user($USER->id, $course->id, $roleid);
  91  
  92          // call as admin and receive all possible fields.
  93          $this->setAdminUser();
  94  
  95          $searchparams = array(
  96              array('key' => 'invalidkey', 'value' => 'invalidkey'),
  97              array('key' => 'email', 'value' => $user1->email),
  98              array('key' => 'firstname', 'value' => $user1->firstname));
  99  
 100          // Call the external function.
 101          $result = core_user_external::get_users($searchparams);
 102  
 103          // We need to execute the return values cleaning process to simulate the web service server
 104          $result = external_api::clean_returnvalue(core_user_external::get_users_returns(), $result);
 105  
 106          // Check we retrieve the good total number of enrolled users + no error on capability.
 107          $expectedreturnedusers = 1;
 108          $returnedusers = $result['users'];
 109          $this->assertEquals($expectedreturnedusers, count($returnedusers));
 110  
 111          foreach($returnedusers as $returneduser) {
 112              $generateduser = ($returneduser['id'] == $USER->id) ?
 113                                  $USER : $generatedusers[$returneduser['id']];
 114              $this->assertEquals($generateduser->username, $returneduser['username']);
 115              if (!empty($generateduser->idnumber)) {
 116                  $this->assertEquals($generateduser->idnumber, $returneduser['idnumber']);
 117              }
 118              $this->assertEquals($generateduser->firstname, $returneduser['firstname']);
 119              $this->assertEquals($generateduser->lastname, $returneduser['lastname']);
 120              if ($generateduser->email != $USER->email) { // Don't check the tmp modified $USER email.
 121                  $this->assertEquals($generateduser->email, $returneduser['email']);
 122              }
 123              if (!empty($generateduser->address)) {
 124                  $this->assertEquals($generateduser->address, $returneduser['address']);
 125              }
 126              if (!empty($generateduser->phone1)) {
 127                  $this->assertEquals($generateduser->phone1, $returneduser['phone1']);
 128              }
 129              if (!empty($generateduser->phone2)) {
 130                  $this->assertEquals($generateduser->phone2, $returneduser['phone2']);
 131              }
 132              if (!empty($generateduser->department)) {
 133                  $this->assertEquals($generateduser->department, $returneduser['department']);
 134              }
 135              if (!empty($generateduser->institution)) {
 136                  $this->assertEquals($generateduser->institution, $returneduser['institution']);
 137              }
 138              if (!empty($generateduser->description)) {
 139                  $this->assertEquals($generateduser->description, $returneduser['description']);
 140              }
 141              if (!empty($generateduser->descriptionformat)) {
 142                  $this->assertEquals(FORMAT_HTML, $returneduser['descriptionformat']);
 143              }
 144              if (!empty($generateduser->city)) {
 145                  $this->assertEquals($generateduser->city, $returneduser['city']);
 146              }
 147              if (!empty($generateduser->country)) {
 148                  $this->assertEquals($generateduser->country, $returneduser['country']);
 149              }
 150              if (!empty($CFG->usetags) and !empty($generateduser->interests)) {
 151                  $this->assertEquals(implode(', ', $generateduser->interests), $returneduser['interests']);
 152              }
 153          }
 154  
 155          // Test the invalid key warning.
 156          $warnings = $result['warnings'];
 157          $this->assertEquals(count($warnings), 1);
 158          $warning = array_pop($warnings);
 159          $this->assertEquals($warning['item'], 'invalidkey');
 160          $this->assertEquals($warning['warningcode'], 'invalidfieldparameter');
 161  
 162          // Test sending twice the same search field.
 163          try {
 164              $searchparams = array(
 165              array('key' => 'firstname', 'value' => 'Canard'),
 166              array('key' => 'email', 'value' => $user1->email),
 167              array('key' => 'firstname', 'value' => $user1->firstname));
 168  
 169              // Call the external function.
 170              $result = core_user_external::get_users($searchparams);
 171              $this->fail('Expecting \'keyalreadyset\' moodle_exception to be thrown.');
 172          } catch (\moodle_exception $e) {
 173              $this->assertEquals('keyalreadyset', $e->errorcode);
 174          } catch (\Exception $e) {
 175              $this->fail('Expecting \'keyalreadyset\' moodle_exception to be thrown.');
 176          }
 177      }
 178  
 179      /**
 180       * Test get_users_by_field
 181       */
 182      public function test_get_users_by_field() {
 183          global $USER, $CFG;
 184  
 185          $this->resetAfterTest(true);
 186  
 187          $generator = self::getDataGenerator();
 188  
 189          // Create complex user profile field supporting multi-lang.
 190          filter_set_global_state('multilang', TEXTFILTER_ON);
 191          $statuses = 'UE\nSE\n<span lang="en" class="multilang">Other</span><span lang="es" class="multilang">Otro</span>';
 192          $generator->create_custom_profile_field(
 193              [
 194                  'datatype' => 'menu',
 195                  'shortname' => 'employmentstatus', 'name' => 'Employment status',
 196                  'param1' => $statuses
 197              ]
 198          );
 199  
 200          $course = $generator->create_course();
 201          $user1 = array(
 202              'username' => 'usernametest1',
 203              'idnumber' => 'idnumbertest1',
 204              'firstname' => 'First Name User Test 1',
 205              'lastname' => 'Last Name User Test 1',
 206              'email' => 'usertest1@example.com',
 207              'address' => '2 Test Street Perth 6000 WA',
 208              'phone1' => '01010101010',
 209              'phone2' => '02020203',
 210              'department' => 'Department of user 1',
 211              'institution' => 'Institution of user 1',
 212              'description' => 'This is a description for user 1',
 213              'descriptionformat' => FORMAT_MOODLE,
 214              'city' => 'Perth',
 215              'country' => 'AU',
 216              'profile_field_jobposition' => 'Manager',
 217              'profile_field_employmentstatus' => explode('\n', $statuses)[2],
 218          );
 219          $user1 = $generator->create_user($user1);
 220          if (!empty($CFG->usetags)) {
 221              require_once($CFG->dirroot . '/user/editlib.php');
 222              $user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
 223              useredit_update_interests($user1, $user1->interests);
 224          }
 225          $user2 = $generator->create_user(
 226                  array('username' => 'usernametest2', 'idnumber' => 'idnumbertest2'));
 227  
 228          $generatedusers = array();
 229          $generatedusers[$user1->id] = $user1;
 230          $generatedusers[$user2->id] = $user2;
 231  
 232          $context = \context_course::instance($course->id);
 233          $roleid = $this->assignUserCapability('moodle/user:viewdetails', $context->id);
 234  
 235          // Enrol the users in the course.
 236          $generator->enrol_user($user1->id, $course->id, $roleid, 'manual');
 237          $generator->enrol_user($user2->id, $course->id, $roleid, 'manual');
 238          $generator->enrol_user($USER->id, $course->id, $roleid, 'manual');
 239  
 240          // call as admin and receive all possible fields.
 241          $this->setAdminUser();
 242  
 243          $fieldstosearch = array('id', 'idnumber', 'username', 'email');
 244  
 245          foreach ($fieldstosearch as $fieldtosearch) {
 246  
 247              // Call the external function.
 248              $returnedusers = core_user_external::get_users_by_field($fieldtosearch,
 249                          array($USER->{$fieldtosearch}, $user1->{$fieldtosearch}, $user2->{$fieldtosearch}));
 250              $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
 251  
 252              // Expected result differ following the searched field
 253              // Admin user in the PHPunit framework doesn't have an idnumber.
 254              if ($fieldtosearch == 'idnumber') {
 255                  $expectedreturnedusers = 2;
 256              } else {
 257                  $expectedreturnedusers = 3;
 258              }
 259  
 260              // Check we retrieve the good total number of enrolled users + no error on capability.
 261              $this->assertEquals($expectedreturnedusers, count($returnedusers));
 262  
 263              foreach($returnedusers as $returneduser) {
 264                  $generateduser = ($returneduser['id'] == $USER->id) ?
 265                                      $USER : $generatedusers[$returneduser['id']];
 266                  $this->assertEquals($generateduser->username, $returneduser['username']);
 267                  if (!empty($generateduser->idnumber)) {
 268                      $this->assertEquals($generateduser->idnumber, $returneduser['idnumber']);
 269                  }
 270                  $this->assertEquals($generateduser->firstname, $returneduser['firstname']);
 271                  $this->assertEquals($generateduser->lastname, $returneduser['lastname']);
 272                  if ($generateduser->email != $USER->email) { //don't check the tmp modified $USER email
 273                      $this->assertEquals($generateduser->email, $returneduser['email']);
 274                  }
 275                  if (!empty($generateduser->address)) {
 276                      $this->assertEquals($generateduser->address, $returneduser['address']);
 277                  }
 278                  if (!empty($generateduser->phone1)) {
 279                      $this->assertEquals($generateduser->phone1, $returneduser['phone1']);
 280                  }
 281                  if (!empty($generateduser->phone2)) {
 282                      $this->assertEquals($generateduser->phone2, $returneduser['phone2']);
 283                  }
 284                  if (!empty($generateduser->department)) {
 285                      $this->assertEquals($generateduser->department, $returneduser['department']);
 286                  }
 287                  if (!empty($generateduser->institution)) {
 288                      $this->assertEquals($generateduser->institution, $returneduser['institution']);
 289                  }
 290                  if (!empty($generateduser->description)) {
 291                      $this->assertEquals($generateduser->description, $returneduser['description']);
 292                  }
 293                  if (!empty($generateduser->descriptionformat) and isset($returneduser['descriptionformat'])) {
 294                      $this->assertEquals($generateduser->descriptionformat, $returneduser['descriptionformat']);
 295                  }
 296                  if (!empty($generateduser->city)) {
 297                      $this->assertEquals($generateduser->city, $returneduser['city']);
 298                  }
 299                  if (!empty($generateduser->country)) {
 300                      $this->assertEquals($generateduser->country, $returneduser['country']);
 301                  }
 302                  if (!empty($CFG->usetags) and !empty($generateduser->interests)) {
 303                      $this->assertEquals(implode(', ', $generateduser->interests), $returneduser['interests']);
 304                  }
 305                  // Default language and no theme were used for the user.
 306                  $this->assertEquals($CFG->lang, $returneduser['lang']);
 307                  $this->assertEmpty($returneduser['theme']);
 308  
 309                  if ($returneduser['id'] == $user1->id) {
 310                      $this->assertCount(1, $returneduser['customfields']);
 311                      $dbvalue = explode('\n', $statuses)[2];
 312                      $this->assertEquals($dbvalue, $returneduser['customfields'][0]['value']);
 313                      $this->assertEquals('Other', $returneduser['customfields'][0]['displayvalue']);
 314                  }
 315              }
 316          }
 317  
 318          // Test that no result are returned for search by username if we are not admin
 319          $this->setGuestUser();
 320  
 321          // Call the external function.
 322          $returnedusers = core_user_external::get_users_by_field('username',
 323                      array($USER->username, $user1->username, $user2->username));
 324          $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
 325  
 326          // Only the own $USER username should be returned
 327          $this->assertEquals(1, count($returnedusers));
 328  
 329          // And finally test as one of the enrolled users.
 330          $this->setUser($user1);
 331  
 332          // Call the external function.
 333          $returnedusers = core_user_external::get_users_by_field('username',
 334              array($USER->username, $user1->username, $user2->username));
 335          $returnedusers = external_api::clean_returnvalue(core_user_external::get_users_by_field_returns(), $returnedusers);
 336  
 337          // Only the own $USER username should be returned still.
 338          $this->assertEquals(1, count($returnedusers));
 339      }
 340  
 341      public function get_course_user_profiles_setup($capability) {
 342          global $USER, $CFG;
 343  
 344          $this->resetAfterTest(true);
 345  
 346          $return = new \stdClass();
 347  
 348          // Create the course and fetch its context.
 349          $return->course = self::getDataGenerator()->create_course();
 350          $return->user1 = array(
 351              'username' => 'usernametest1',
 352              'idnumber' => 'idnumbertest1',
 353              'firstname' => 'First Name User Test 1',
 354              'lastname' => 'Last Name User Test 1',
 355              'email' => 'usertest1@example.com',
 356              'address' => '2 Test Street Perth 6000 WA',
 357              'phone1' => '01010101010',
 358              'phone2' => '02020203',
 359              'department' => 'Department of user 1',
 360              'institution' => 'Institution of user 1',
 361              'description' => 'This is a description for user 1',
 362              'descriptionformat' => FORMAT_MOODLE,
 363              'city' => 'Perth',
 364              'country' => 'AU'
 365          );
 366          $return->user1 = self::getDataGenerator()->create_user($return->user1);
 367          if (!empty($CFG->usetags)) {
 368              require_once($CFG->dirroot . '/user/editlib.php');
 369              $return->user1->interests = array('Cinema', 'Tennis', 'Dance', 'Guitar', 'Cooking');
 370              useredit_update_interests($return->user1, $return->user1->interests);
 371          }
 372          $return->user2 = self::getDataGenerator()->create_user();
 373  
 374          $context = \context_course::instance($return->course->id);
 375          $return->roleid = $this->assignUserCapability($capability, $context->id);
 376  
 377          // Enrol the users in the course.
 378          $this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
 379          $this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
 380          $this->getDataGenerator()->enrol_user($USER->id, $return->course->id, $return->roleid, 'manual');
 381  
 382          $group1 = $this->getDataGenerator()->create_group(['courseid' => $return->course->id, 'name' => 'G1']);
 383          $group2 = $this->getDataGenerator()->create_group(['courseid' => $return->course->id, 'name' => 'G2']);
 384  
 385          groups_add_member($group1->id, $return->user1->id);
 386          groups_add_member($group2->id, $return->user2->id);
 387  
 388          return $return;
 389      }
 390  
 391      /**
 392       * Test get_course_user_profiles
 393       */
 394      public function test_get_course_user_profiles() {
 395          global $USER, $CFG;
 396  
 397          $this->resetAfterTest(true);
 398  
 399          $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
 400  
 401          // Call the external function.
 402          $enrolledusers = core_user_external::get_course_user_profiles(array(
 403                      array('userid' => $USER->id, 'courseid' => $data->course->id)));
 404  
 405          // We need to execute the return values cleaning process to simulate the web service server.
 406          $enrolledusers = external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
 407  
 408          // Check we retrieve the good total number of enrolled users + no error on capability.
 409          $this->assertEquals(1, count($enrolledusers));
 410      }
 411  
 412      public function test_get_user_course_profile_as_admin() {
 413          global $USER, $CFG;
 414  
 415          global $USER, $CFG;
 416  
 417          $this->resetAfterTest(true);
 418  
 419          $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
 420  
 421          // Do the same call as admin to receive all possible fields.
 422          $this->setAdminUser();
 423          $USER->email = "admin@example.com";
 424  
 425          // Call the external function.
 426          $enrolledusers = core_user_external::get_course_user_profiles(array(
 427              array('userid' => $data->user1->id, 'courseid' => $data->course->id)));
 428  
 429          // We need to execute the return values cleaning process to simulate the web service server.
 430          $enrolledusers = external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
 431          // Check we get the requested user and that is in a group.
 432          $this->assertCount(1, $enrolledusers);
 433          $this->assertCount(1, $enrolledusers[0]['groups']);
 434  
 435          foreach($enrolledusers as $enrolleduser) {
 436              if ($enrolleduser['username'] == $data->user1->username) {
 437                  $this->assertEquals($data->user1->idnumber, $enrolleduser['idnumber']);
 438                  $this->assertEquals($data->user1->firstname, $enrolleduser['firstname']);
 439                  $this->assertEquals($data->user1->lastname, $enrolleduser['lastname']);
 440                  $this->assertEquals($data->user1->email, $enrolleduser['email']);
 441                  $this->assertEquals($data->user1->address, $enrolleduser['address']);
 442                  $this->assertEquals($data->user1->phone1, $enrolleduser['phone1']);
 443                  $this->assertEquals($data->user1->phone2, $enrolleduser['phone2']);
 444                  $this->assertEquals($data->user1->department, $enrolleduser['department']);
 445                  $this->assertEquals($data->user1->institution, $enrolleduser['institution']);
 446                  $this->assertEquals($data->user1->description, $enrolleduser['description']);
 447                  $this->assertEquals(FORMAT_HTML, $enrolleduser['descriptionformat']);
 448                  $this->assertEquals($data->user1->city, $enrolleduser['city']);
 449                  $this->assertEquals($data->user1->country, $enrolleduser['country']);
 450                  if (!empty($CFG->usetags)) {
 451                      $this->assertEquals(implode(', ', $data->user1->interests), $enrolleduser['interests']);
 452                  }
 453              }
 454          }
 455      }
 456  
 457      /**
 458       * Test create_users
 459       */
 460      public function test_create_users() {
 461          global $DB;
 462  
 463          $this->resetAfterTest(true);
 464  
 465          $user1 = array(
 466              'username' => 'usernametest1',
 467              'password' => 'Moodle2012!',
 468              'idnumber' => 'idnumbertest1',
 469              'firstname' => 'First Name User Test 1',
 470              'lastname' => 'Last Name User Test 1',
 471              'middlename' => 'Middle Name User Test 1',
 472              'lastnamephonetic' => '最後のお名前のテスト一号',
 473              'firstnamephonetic' => 'お名前のテスト一号',
 474              'alternatename' => 'Alternate Name User Test 1',
 475              'email' => 'usertest1@example.com',
 476              'description' => 'This is a description for user 1',
 477              'city' => 'Perth',
 478              'country' => 'AU',
 479              'preferences' => [[
 480                      'type' => 'htmleditor',
 481                      'value' => 'atto'
 482                  ], [
 483                      'type' => 'invalidpreference',
 484                      'value' => 'abcd'
 485                  ]
 486              ],
 487              'department' => 'College of Science',
 488              'institution' => 'National Institute of Physics',
 489              'phone1' => '01 2345 6789',
 490              'maildisplay' => 1,
 491              'interests' => 'badminton, basketball, cooking,  '
 492          );
 493  
 494          // User with an authentication method done externally.
 495          $user2 = array(
 496              'username' => 'usernametest2',
 497              'firstname' => 'First Name User Test 2',
 498              'lastname' => 'Last Name User Test 2',
 499              'email' => 'usertest2@example.com',
 500              'auth' => 'oauth2'
 501          );
 502  
 503          $context = \context_system::instance();
 504          $roleid = $this->assignUserCapability('moodle/user:create', $context->id);
 505          $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
 506  
 507          // Call the external function.
 508          $createdusers = core_user_external::create_users(array($user1, $user2));
 509  
 510          // We need to execute the return values cleaning process to simulate the web service server.
 511          $createdusers = external_api::clean_returnvalue(core_user_external::create_users_returns(), $createdusers);
 512  
 513          // Check we retrieve the good total number of created users + no error on capability.
 514          $this->assertCount(2, $createdusers);
 515  
 516          foreach($createdusers as $createduser) {
 517              $dbuser = $DB->get_record('user', array('id' => $createduser['id']));
 518  
 519              if ($createduser['username'] === $user1['username']) {
 520                  $usertotest = $user1;
 521                  $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
 522                  $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
 523                  // Confirm user interests have been saved.
 524                  $interests = \core_tag_tag::get_item_tags_array('core', 'user', $createduser['id'],
 525                          \core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
 526                  // There should be 3 user interests.
 527                  $this->assertCount(3, $interests);
 528  
 529              } else if ($createduser['username'] === $user2['username']) {
 530                  $usertotest = $user2;
 531              }
 532  
 533              foreach ($dbuser as $property => $value) {
 534                  if ($property === 'password') {
 535                      if ($usertotest === $user2) {
 536                          // External auth mechanisms don't store password in the user table.
 537                          $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $value);
 538                      } else {
 539                          // Skip hashed passwords.
 540                          continue;
 541                      }
 542                  }
 543                  // Confirm that the values match.
 544                  if (isset($usertotest[$property])) {
 545                      $this->assertEquals($usertotest[$property], $value);
 546                  }
 547              }
 548          }
 549  
 550          // Call without required capability
 551          $this->unassignUserCapability('moodle/user:create', $context->id, $roleid);
 552          $this->expectException('required_capability_exception');
 553          core_user_external::create_users(array($user1));
 554      }
 555  
 556      /**
 557       * Test create_users with password and createpassword parameter not set.
 558       */
 559      public function test_create_users_empty_password() {
 560          $this->resetAfterTest();
 561          $this->setAdminUser();
 562  
 563          $user = [
 564              'username' => 'usernametest1',
 565              'firstname' => 'First Name User Test 1',
 566              'lastname' => 'Last Name User Test 1',
 567              'email' => 'usertest1@example.com',
 568          ];
 569  
 570          // This should throw an exception because either password or createpassword param must be passed for auth_manual.
 571          $this->expectException(\invalid_parameter_exception::class);
 572          core_user_external::create_users([$user]);
 573      }
 574  
 575      /**
 576       * Data provider for \core_user_externallib_testcase::test_create_users_with_same_emails().
 577       */
 578      public function create_users_provider_with_same_emails() {
 579          return [
 580              'Same emails allowed, same case' => [
 581                  1, false
 582              ],
 583              'Same emails allowed, different case' => [
 584                  1, true
 585              ],
 586              'Same emails disallowed, same case' => [
 587                  0, false
 588              ],
 589              'Same emails disallowed, different case' => [
 590                  0, true
 591              ],
 592          ];
 593      }
 594  
 595      /**
 596       * Test for \core_user_external::create_users() when user using the same email addresses are being created.
 597       *
 598       * @dataProvider create_users_provider_with_same_emails
 599       * @param int $sameemailallowed The value to set for $CFG->allowaccountssameemail.
 600       * @param boolean $differentcase Whether to user a different case for the other user.
 601       */
 602      public function test_create_users_with_same_emails($sameemailallowed, $differentcase) {
 603          global $DB;
 604  
 605          $this->resetAfterTest();
 606          $this->setAdminUser();
 607  
 608          // Allow multiple users with the same email address.
 609          set_config('allowaccountssameemail', $sameemailallowed);
 610          $users = [
 611              [
 612                  'username' => 's1',
 613                  'firstname' => 'Johnny',
 614                  'lastname' => 'Bravo',
 615                  'email' => 's1@example.com',
 616                  'password' => 'Passw0rd!'
 617              ],
 618              [
 619                  'username' => 's2',
 620                  'firstname' => 'John',
 621                  'lastname' => 'Doe',
 622                  'email' => $differentcase ? 'S1@EXAMPLE.COM' : 's1@example.com',
 623                  'password' => 'Passw0rd!'
 624              ],
 625          ];
 626  
 627          if (!$sameemailallowed) {
 628              // This should throw an exception when $CFG->allowaccountssameemail is empty.
 629              $this->expectException(\invalid_parameter_exception::class);
 630          }
 631  
 632          // Create our users.
 633          core_user_external::create_users($users);
 634  
 635          // Confirm that the users have been created.
 636          list($insql, $params) = $DB->get_in_or_equal(['s1', 's2']);
 637          $this->assertEquals(2, $DB->count_records_select('user', 'username ' . $insql, $params));
 638      }
 639  
 640      /**
 641       * Test create_users with invalid parameters
 642       *
 643       * @dataProvider data_create_users_invalid_parameter
 644       * @param array $data User data to attempt to register.
 645       * @param string $expectmessage Expected exception message.
 646       */
 647      public function test_create_users_invalid_parameter(array $data, $expectmessage) {
 648          global $USER, $CFG, $DB;
 649  
 650          $this->resetAfterTest(true);
 651          $this->assignUserCapability('moodle/user:create', SYSCONTEXTID);
 652  
 653          $this->expectException('invalid_parameter_exception');
 654          $this->expectExceptionMessage($expectmessage);
 655  
 656          core_user_external::create_users(array($data));
 657      }
 658  
 659      /**
 660       * Data provider for {@see self::test_create_users_invalid_parameter()}.
 661       *
 662       * @return array
 663       */
 664      public function data_create_users_invalid_parameter() {
 665          return [
 666              'blank_username' => [
 667                  'data' => [
 668                      'username' => '',
 669                      'firstname' => 'Foo',
 670                      'lastname' => 'Bar',
 671                      'email' => 'foobar@example.com',
 672                      'createpassword' => 1,
 673                  ],
 674                  'expectmessage' => 'The field username cannot be blank',
 675              ],
 676              'blank_firtname' => [
 677                  'data' => [
 678                      'username' => 'foobar',
 679                      'firstname' => "\t \n",
 680                      'lastname' => 'Bar',
 681                      'email' => 'foobar@example.com',
 682                      'createpassword' => 1,
 683                  ],
 684                  'expectmessage' => 'The field firstname cannot be blank',
 685              ],
 686              'blank_lastname' => [
 687                  'data' => [
 688                      'username' => 'foobar',
 689                      'firstname' => '0',
 690                      'lastname' => '   ',
 691                      'email' => 'foobar@example.com',
 692                      'createpassword' => 1,
 693                  ],
 694                  'expectmessage' => 'The field lastname cannot be blank',
 695              ],
 696              'invalid_email' => [
 697                  'data' => [
 698                      'username' => 'foobar',
 699                      'firstname' => 'Foo',
 700                      'lastname' => 'Bar',
 701                      'email' => '@foobar',
 702                      'createpassword' => 1,
 703                  ],
 704                  'expectmessage' => 'Email address is invalid',
 705              ],
 706              'missing_password' => [
 707                  'data' => [
 708                      'username' => 'foobar',
 709                      'firstname' => 'Foo',
 710                      'lastname' => 'Bar',
 711                      'email' => 'foobar@example.com',
 712                  ],
 713                  'expectmessage' => 'Invalid password: you must provide a password, or set createpassword',
 714              ],
 715          ];
 716      }
 717  
 718      /**
 719       * Test delete_users
 720       */
 721      public function test_delete_users() {
 722          global $USER, $CFG, $DB;
 723  
 724          $this->resetAfterTest(true);
 725  
 726          $user1 = self::getDataGenerator()->create_user();
 727          $user2 = self::getDataGenerator()->create_user();
 728  
 729          // Check the users were correctly created.
 730          $this->assertEquals(2, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
 731                  array('userid1' => $user1->id, 'userid2' => $user2->id)));
 732  
 733          $context = \context_system::instance();
 734          $roleid = $this->assignUserCapability('moodle/user:delete', $context->id);
 735  
 736          // Call the external function.
 737          core_user_external::delete_users(array($user1->id, $user2->id));
 738  
 739          // Check we retrieve no users + no error on capability.
 740          $this->assertEquals(0, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
 741                  array('userid1' => $user1->id, 'userid2' => $user2->id)));
 742  
 743          // Call without required capability.
 744          $this->unassignUserCapability('moodle/user:delete', $context->id, $roleid);
 745          $this->expectException('required_capability_exception');
 746          core_user_external::delete_users(array($user1->id, $user2->id));
 747      }
 748  
 749      /**
 750       * Test update_users
 751       */
 752      public function test_update_users() {
 753          global $USER, $CFG, $DB;
 754  
 755          $this->resetAfterTest(true);
 756          $this->preventResetByRollback();
 757  
 758          $wsuser = self::getDataGenerator()->create_user();
 759          self::setUser($wsuser);
 760  
 761          $context = \context_user::instance($USER->id);
 762          $contextid = $context->id;
 763          $filename = "reddot.png";
 764          $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
 765              . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 766  
 767          // Call the files api to create a file.
 768          $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/',
 769                  $filename, $filecontent, null, null);
 770          $draftfile = external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
 771  
 772          $draftid = $draftfile['itemid'];
 773  
 774          $user1 = self::getDataGenerator()->create_user();
 775  
 776          $user1 = array(
 777              'id' => $user1->id,
 778              'username' => 'usernametest1',
 779              'password' => 'Moodle2012!',
 780              'idnumber' => 'idnumbertest1',
 781              'firstname' => 'First Name User Test 1',
 782              'lastname' => 'Last Name User Test 1',
 783              'middlename' => 'Middle Name User Test 1',
 784              'lastnamephonetic' => '最後のお名前のテスト一号',
 785              'firstnamephonetic' => 'お名前のテスト一号',
 786              'alternatename' => 'Alternate Name User Test 1',
 787              'email' => 'usertest1@example.com',
 788              'description' => 'This is a description for user 1',
 789              'city' => 'Perth',
 790              'userpicture' => $draftid,
 791              'country' => 'AU',
 792              'preferences' => [[
 793                      'type' => 'htmleditor',
 794                      'value' => 'atto'
 795                  ], [
 796                      'type' => 'invialidpreference',
 797                      'value' => 'abcd'
 798                  ]
 799              ],
 800              'department' => 'College of Science',
 801              'institution' => 'National Institute of Physics',
 802              'phone1' => '01 2345 6789',
 803              'maildisplay' => 1,
 804              'interests' => 'badminton, basketball, cooking,  '
 805          );
 806  
 807          $context = \context_system::instance();
 808          $roleid = $this->assignUserCapability('moodle/user:update', $context->id);
 809          $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
 810  
 811          // Check we can't update deleted users, guest users, site admin.
 812          $user2 = $user3 = $user4 = $user1;
 813          $user2['id'] = $CFG->siteguest;
 814  
 815          $siteadmins = explode(',', $CFG->siteadmins);
 816          $user3['id'] = array_shift($siteadmins);
 817  
 818          $userdeleted = self::getDataGenerator()->create_user();
 819          $user4['id'] = $userdeleted->id;
 820          user_delete_user($userdeleted);
 821  
 822          $user5 = self::getDataGenerator()->create_user();
 823          $user5 = array('id' => $user5->id, 'email' => $user5->email);
 824  
 825          // Call the external function.
 826          $returnvalue = core_user_external::update_users(array($user1, $user2, $user3, $user4));
 827          $returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
 828  
 829          // Check warnings.
 830          $this->assertEquals($user2['id'], $returnvalue['warnings'][0]['itemid']); // Guest user.
 831          $this->assertEquals('usernotupdatedguest', $returnvalue['warnings'][0]['warningcode']);
 832          $this->assertEquals($user3['id'], $returnvalue['warnings'][1]['itemid']); // Admin user.
 833          $this->assertEquals('usernotupdatedadmin', $returnvalue['warnings'][1]['warningcode']);
 834          $this->assertEquals($user4['id'], $returnvalue['warnings'][2]['itemid']); // Deleted user.
 835          $this->assertEquals('usernotupdateddeleted', $returnvalue['warnings'][2]['warningcode']);
 836  
 837          $dbuser2 = $DB->get_record('user', array('id' => $user2['id']));
 838          $this->assertNotEquals($dbuser2->username, $user2['username']);
 839          $dbuser3 = $DB->get_record('user', array('id' => $user3['id']));
 840          $this->assertNotEquals($dbuser3->username, $user3['username']);
 841          $dbuser4 = $DB->get_record('user', array('id' => $user4['id']));
 842          $this->assertNotEquals($dbuser4->username, $user4['username']);
 843  
 844          $dbuser = $DB->get_record('user', array('id' => $user1['id']));
 845          $this->assertEquals($dbuser->username, $user1['username']);
 846          $this->assertEquals($dbuser->idnumber, $user1['idnumber']);
 847          $this->assertEquals($dbuser->firstname, $user1['firstname']);
 848          $this->assertEquals($dbuser->lastname, $user1['lastname']);
 849          $this->assertEquals($dbuser->email, $user1['email']);
 850          $this->assertEquals($dbuser->description, $user1['description']);
 851          $this->assertEquals($dbuser->city, $user1['city']);
 852          $this->assertEquals($dbuser->country, $user1['country']);
 853          $this->assertNotEquals(0, $dbuser->picture, 'Picture must be set to the new icon itemid for this user');
 854          $this->assertEquals($dbuser->department, $user1['department']);
 855          $this->assertEquals($dbuser->institution, $user1['institution']);
 856          $this->assertEquals($dbuser->phone1, $user1['phone1']);
 857          $this->assertEquals($dbuser->maildisplay, $user1['maildisplay']);
 858          $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
 859          $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
 860  
 861          // Confirm user interests have been saved.
 862          $interests = \core_tag_tag::get_item_tags_array('core', 'user', $user1['id'], \core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
 863          // There should be 3 user interests.
 864          $this->assertCount(3, $interests);
 865  
 866          // Confirm no picture change when parameter is not supplied.
 867          unset($user1['userpicture']);
 868          core_user_external::update_users(array($user1));
 869          $dbusernopic = $DB->get_record('user', array('id' => $user1['id']));
 870          $this->assertEquals($dbuser->picture, $dbusernopic->picture, 'Picture not change without the parameter.');
 871  
 872          // Confirm delete of picture deletes the picture from the user record.
 873          $user1['userpicture'] = 0;
 874          core_user_external::update_users(array($user1));
 875          $dbuserdelpic = $DB->get_record('user', array('id' => $user1['id']));
 876          $this->assertEquals(0, $dbuserdelpic->picture, 'Picture must be deleted when sent as 0.');
 877  
 878          // Updating user with an invalid email.
 879          $user5['email'] = 'bogus';
 880          $returnvalue = core_user_external::update_users(array($user5));
 881          $returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
 882          $this->assertEquals('useremailinvalid', $returnvalue['warnings'][0]['warningcode']);
 883          $this->assertStringContainsString('Invalid email address',
 884              $returnvalue['warnings'][0]['message']);
 885  
 886          // Updating user with a duplicate email.
 887          $user5['email'] = $user1['email'];
 888          $returnvalue = core_user_external::update_users(array($user1, $user5));
 889          $returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
 890          $this->assertEquals('useremailduplicate', $returnvalue['warnings'][0]['warningcode']);
 891          $this->assertStringContainsString('Duplicate email address',
 892                  $returnvalue['warnings'][0]['message']);
 893  
 894          // Updating a user that does not exist.
 895          $user5['id'] = -1;
 896          $returnvalue = core_user_external::update_users(array($user5));
 897          $returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
 898          $this->assertEquals('invaliduserid', $returnvalue['warnings'][0]['warningcode']);
 899          $this->assertStringContainsString('Invalid user ID',
 900                  $returnvalue['warnings'][0]['message']);
 901  
 902          // Updating a remote user.
 903          $user1['mnethostid'] = 5;
 904          user_update_user($user1); // Update user not using webservice.
 905          unset($user1['mnethostid']); // The mnet host ID field is not in the allowed field list for the webservice.
 906          $returnvalue = core_user_external::update_users(array($user1));
 907          $returnvalue = external_api::clean_returnvalue(core_user_external::update_users_returns(), $returnvalue);
 908          $this->assertEquals('usernotupdatedremote', $returnvalue['warnings'][0]['warningcode']);
 909          $this->assertStringContainsString('User is a remote user',
 910                  $returnvalue['warnings'][0]['message']);
 911  
 912          // Call without required capability.
 913          $this->unassignUserCapability('moodle/user:update', $context->id, $roleid);
 914          $this->expectException('required_capability_exception');
 915          core_user_external::update_users(array($user1));
 916      }
 917  
 918      /**
 919       * Data provider for testing \core_user_external::update_users() for users with same emails
 920       *
 921       * @return array
 922       */
 923      public function users_with_same_emails() {
 924          return [
 925              'Same emails not allowed: Update name using exactly the same email' => [
 926                  0, 'John', 's1@example.com', 'Johnny', 's1@example.com', false, true
 927              ],
 928              'Same emails not allowed: Update using someone else\'s email' => [
 929                  0, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, false
 930              ],
 931              'Same emails allowed: Update using someone else\'s email' => [
 932                  1, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, true
 933              ],
 934              'Same emails not allowed: Update using same email but with different case' => [
 935                  0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', false, true
 936              ],
 937              'Same emails not allowed: Update using another user\'s email similar to user but with different case' => [
 938                  0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, false
 939              ],
 940              'Same emails allowed: Update using another user\'s email similar to user but with different case' => [
 941                  1, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, true
 942              ],
 943          ];
 944      }
 945  
 946      /**
 947       * Test update_users using similar emails with varying cases.
 948       *
 949       * @dataProvider users_with_same_emails
 950       * @param boolean $allowsameemail The value to set for $CFG->allowaccountssameemail.
 951       * @param string $currentname The user's current name.
 952       * @param string $currentemail The user's current email.
 953       * @param string $newname The user's new name.
 954       * @param string $newemail The user's new email.
 955       * @param boolean $withanotheruser Whether to create another user that has the same email as the target user's new email.
 956       * @param boolean $successexpected Whether we expect that the target user's email/name will be updated.
 957       */
 958      public function test_update_users_emails_with_different_cases($allowsameemail, $currentname, $currentemail,
 959                                                                    $newname, $newemail, $withanotheruser, $successexpected) {
 960          global $DB;
 961  
 962          $this->resetAfterTest();
 963          $this->setAdminUser();
 964  
 965          // Set the value for $CFG->allowaccountssameemail.
 966          set_config('allowaccountssameemail', $allowsameemail);
 967  
 968          $generator = self::getDataGenerator();
 969  
 970          // Create the user that we wish to update.
 971          $usertoupdate = $generator->create_user(['email' => $currentemail, 'firstname' => $currentname]);
 972  
 973          if ($withanotheruser) {
 974              // Create another user that has the same email as the new email that we'd like to update for our target user.
 975              $generator->create_user(['email' => $newemail]);
 976          }
 977  
 978          // Build the user update parameters.
 979          $updateparams = [
 980              'id' => $usertoupdate->id,
 981              'email' => $newemail,
 982              'firstname' => $newname
 983          ];
 984          // Let's try to update the user's information.
 985          core_user_external::update_users([$updateparams]);
 986  
 987          // Fetch the updated user record.
 988          $userrecord = $DB->get_record('user', ['id' => $usertoupdate->id], 'id, email, firstname');
 989  
 990          // If we expect the update to succeed, then the email/name would have been changed.
 991          if ($successexpected) {
 992              $expectedemail = $newemail;
 993              $expectedname = $newname;
 994          } else {
 995              $expectedemail = $currentemail;
 996              $expectedname = $currentname;
 997          }
 998          // Confirm that our expectations are met.
 999          $this->assertEquals($expectedemail, $userrecord->email);
1000          $this->assertEquals($expectedname, $userrecord->firstname);
1001      }
1002  
1003      /**
1004       * Test add_user_private_files
1005       */
1006      public function test_add_user_private_files() {
1007          global $USER, $CFG, $DB;
1008  
1009          $this->resetAfterTest(true);
1010  
1011          $context = \context_system::instance();
1012          $roleid = $this->assignUserCapability('moodle/user:manageownfiles', $context->id);
1013  
1014          $context = \context_user::instance($USER->id);
1015          $contextid = $context->id;
1016          $component = "user";
1017          $filearea = "draft";
1018          $itemid = 0;
1019          $filepath = "/";
1020          $filename = "Simple.txt";
1021          $filecontent = base64_encode("Let us create a nice simple file");
1022          $contextlevel = null;
1023          $instanceid = null;
1024          $browser = get_file_browser();
1025  
1026          // Call the files api to create a file.
1027          $draftfile = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
1028                                                   $filename, $filecontent, $contextlevel, $instanceid);
1029          $draftfile = external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
1030  
1031          $draftid = $draftfile['itemid'];
1032          // Make sure the file was created.
1033          $file = $browser->get_file_info($context, $component, $filearea, $draftid, $filepath, $filename);
1034          $this->assertNotEmpty($file);
1035  
1036          // Make sure the file does not exist in the user private files.
1037          $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
1038          $this->assertEmpty($file);
1039  
1040          // Call the external function.
1041          core_user_external::add_user_private_files($draftid);
1042  
1043          // Make sure the file was added to the user private files.
1044          $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
1045          $this->assertNotEmpty($file);
1046      }
1047  
1048  
1049      /**
1050       * Test add_user_private_files quota
1051       */
1052      public function test_add_user_private_files_quota() {
1053          global $USER, $CFG, $DB;
1054  
1055          $this->resetAfterTest(true);
1056  
1057          $context = \context_system::instance();
1058          $roleid = $this->assignUserCapability('moodle/user:manageownfiles', $context->id);
1059  
1060          $context = \context_user::instance($USER->id);
1061          $contextid = $context->id;
1062          $component = "user";
1063          $filearea = "draft";
1064          $itemid = 0;
1065          $filepath = "/";
1066          $filename = "Simple.txt";
1067          $filecontent = base64_encode("Let us create a nice simple file");
1068          $contextlevel = null;
1069          $instanceid = null;
1070          $browser = get_file_browser();
1071  
1072          // Call the files api to create a file.
1073          $draftfile = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
1074              $filename, $filecontent, $contextlevel, $instanceid);
1075          $draftfile = external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
1076          $draftid = $draftfile['itemid'];
1077  
1078          // Call the external function to add the file to private files.
1079          core_user_external::add_user_private_files($draftid);
1080  
1081          // Force the quota so we are sure it won't be space to add the new file.
1082          $fileareainfo = file_get_file_area_info($contextid, 'user', 'private');
1083          $CFG->userquota = $fileareainfo['filesize_without_references'] + 1;
1084  
1085          // Generate a new draftitemid for the same testfile.
1086          $draftfile = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
1087              $filename, $filecontent, $contextlevel, $instanceid);
1088          $draftid = $draftfile['itemid'];
1089  
1090          $this->expectException('moodle_exception');
1091          $this->expectExceptionMessage(get_string('maxareabytes', 'error'));
1092  
1093          // Call the external function to include the new file.
1094          core_user_external::add_user_private_files($draftid);
1095      }
1096  
1097      /**
1098       * Test add user device
1099       */
1100      public function test_add_user_device() {
1101          global $USER, $CFG, $DB;
1102  
1103          $this->resetAfterTest(true);
1104  
1105          $device = array(
1106                  'appid' => 'com.moodle.moodlemobile',
1107                  'name' => 'occam',
1108                  'model' => 'Nexus 4',
1109                  'platform' => 'Android',
1110                  'version' => '4.2.2',
1111                  'pushid' => 'apushdkasdfj4835',
1112                  'uuid' => 'asdnfl348qlksfaasef859',
1113                  'publickey' => null,
1114                  );
1115  
1116          // Call the external function.
1117          core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1118                                              $device['version'], $device['pushid'], $device['uuid']);
1119  
1120          $created = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
1121          $created = (array) $created;
1122  
1123          $this->assertEquals($device, array_intersect_key((array)$created, $device));
1124  
1125          // Test reuse the same pushid value.
1126          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1127                                                          $device['version'], $device['pushid'], $device['uuid']);
1128          // We need to execute the return values cleaning process to simulate the web service server.
1129          $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1130          $this->assertCount(1, $warnings);
1131  
1132          // Test update an existing device.
1133          $device['pushid'] = 'different than before';
1134          $device['publickey'] = 'MFsxCzAJBgNVBAYTAkZSMRMwEQYDVQQ';
1135          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1136              $device['version'], $device['pushid'], $device['uuid'], $device['publickey']);
1137          $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1138  
1139          $this->assertEquals(1, $DB->count_records('user_devices'));
1140          $updated = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
1141          $this->assertEquals($device, array_intersect_key((array)$updated, $device));
1142  
1143          // Test creating a new device just changing the uuid.
1144          $device['uuid'] = 'newuidforthesameuser';
1145          $device['pushid'] = 'new different than before';
1146          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1147                                                          $device['version'], $device['pushid'], $device['uuid']);
1148          $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1149          $this->assertEquals(2, $DB->count_records('user_devices'));
1150      }
1151  
1152      /**
1153       * Test remove user device
1154       */
1155      public function test_remove_user_device() {
1156          global $USER, $CFG, $DB;
1157  
1158          $this->resetAfterTest(true);
1159  
1160          $device = array(
1161                  'appid' => 'com.moodle.moodlemobile',
1162                  'name' => 'occam',
1163                  'model' => 'Nexus 4',
1164                  'platform' => 'Android',
1165                  'version' => '4.2.2',
1166                  'pushid' => 'apushdkasdfj4835',
1167                  'uuid' => 'ABCDE3723ksdfhasfaasef859'
1168                  );
1169  
1170          // A device with the same properties except the appid and pushid.
1171          $device2 = $device;
1172          $device2['pushid'] = "0987654321";
1173          $device2['appid'] = "other.app.com";
1174  
1175          $this->setAdminUser();
1176          // Create a user device using the external API function.
1177          core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1178                                              $device['version'], $device['pushid'], $device['uuid']);
1179  
1180          // Create the same device but for a different app.
1181          core_user_external::add_user_device($device2['appid'], $device2['name'], $device2['model'], $device2['platform'],
1182                                              $device2['version'], $device2['pushid'], $device2['uuid']);
1183  
1184          // Try to remove a device that does not exist.
1185          $result = core_user_external::remove_user_device('1234567890');
1186          $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1187          $this->assertFalse($result['removed']);
1188          $this->assertCount(1, $result['warnings']);
1189  
1190          // Try to remove a device that does not exist for an existing app.
1191          $result = core_user_external::remove_user_device('1234567890', $device['appid']);
1192          $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1193          $this->assertFalse($result['removed']);
1194          $this->assertCount(1, $result['warnings']);
1195  
1196          // Remove an existing device for an existing app. This will remove one of the two devices.
1197          $result = core_user_external::remove_user_device($device['uuid'], $device['appid']);
1198          $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1199          $this->assertTrue($result['removed']);
1200  
1201          // Remove all the devices. This must remove the remaining device.
1202          $result = core_user_external::remove_user_device($device['uuid']);
1203          $result = external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1204          $this->assertTrue($result['removed']);
1205      }
1206  
1207      /**
1208       * Test get_user_preferences
1209       */
1210      public function test_get_user_preferences() {
1211          $this->resetAfterTest(true);
1212  
1213          $user = self::getDataGenerator()->create_user();
1214          set_user_preference('calendar_maxevents', 1, $user);
1215          set_user_preference('some_random_text', 'text', $user);
1216  
1217          $this->setUser($user);
1218  
1219          $result = core_user_external::get_user_preferences();
1220          $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1221          $this->assertCount(0, $result['warnings']);
1222          // Expect 3, _lastloaded is always returned.
1223          $this->assertCount(3, $result['preferences']);
1224  
1225          foreach ($result['preferences'] as $pref) {
1226              if ($pref['name'] === '_lastloaded') {
1227                  continue;
1228              }
1229              // Check we receive the expected preferences.
1230              $this->assertEquals(get_user_preferences($pref['name']), $pref['value']);
1231          }
1232  
1233          // Retrieve just one preference.
1234          $result = core_user_external::get_user_preferences('some_random_text');
1235          $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1236          $this->assertCount(0, $result['warnings']);
1237          $this->assertCount(1, $result['preferences']);
1238          $this->assertEquals('text', $result['preferences'][0]['value']);
1239  
1240          // Retrieve non-existent preference.
1241          $result = core_user_external::get_user_preferences('non_existent');
1242          $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1243          $this->assertCount(0, $result['warnings']);
1244          $this->assertCount(1, $result['preferences']);
1245          $this->assertEquals(null, $result['preferences'][0]['value']);
1246  
1247          // Check that as admin we can retrieve all the preferences for any user.
1248          $this->setAdminUser();
1249          $result = core_user_external::get_user_preferences('', $user->id);
1250          $result = external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1251          $this->assertCount(0, $result['warnings']);
1252          $this->assertCount(3, $result['preferences']);
1253  
1254          foreach ($result['preferences'] as $pref) {
1255              if ($pref['name'] === '_lastloaded') {
1256                  continue;
1257              }
1258              // Check we receive the expected preferences.
1259              $this->assertEquals(get_user_preferences($pref['name'], null, $user), $pref['value']);
1260          }
1261  
1262          // Check that as a non admin user we cannot retrieve other users preferences.
1263          $anotheruser = self::getDataGenerator()->create_user();
1264          $this->setUser($anotheruser);
1265  
1266          $this->expectException('required_capability_exception');
1267          $result = core_user_external::get_user_preferences('', $user->id);
1268      }
1269  
1270      /**
1271       * Test update_picture
1272       */
1273      public function test_update_picture() {
1274          global $DB, $USER;
1275  
1276          $this->resetAfterTest(true);
1277  
1278          $user = self::getDataGenerator()->create_user();
1279          self::setUser($user);
1280  
1281          $context = \context_user::instance($USER->id);
1282          $contextid = $context->id;
1283          $filename = "reddot.png";
1284          $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
1285              . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
1286  
1287          // Call the files api to create a file.
1288          $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1289          $draftid = $draftfile['itemid'];
1290  
1291          // Change user profile image.
1292          $result = core_user_external::update_picture($draftid);
1293          $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1294          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1295          // The new revision is in the url for the user.
1296          $this->assertStringContainsString($picture, $result['profileimageurl']);
1297          // Check expected URL for serving the image.
1298          $this->assertStringContainsString("/$contextid/user/icon", $result['profileimageurl']);
1299  
1300          // Delete image.
1301          $result = core_user_external::update_picture(0, true);
1302          $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1303          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1304          // No picture.
1305          $this->assertEquals(0, $picture);
1306  
1307          // Add again the user profile image (as admin).
1308          $this->setAdminUser();
1309  
1310          $context = \context_user::instance($USER->id);
1311          $admincontextid = $context->id;
1312          $draftfile = core_files_external::upload($admincontextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1313          $draftid = $draftfile['itemid'];
1314  
1315          $result = core_user_external::update_picture($draftid, false, $user->id);
1316          $result = external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1317          // The new revision is in the url for the user.
1318          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1319          $this->assertStringContainsString($picture, $result['profileimageurl']);
1320          $this->assertStringContainsString("/$contextid/user/icon", $result['profileimageurl']);
1321      }
1322  
1323      /**
1324       * Test update_picture disabled
1325       */
1326      public function test_update_picture_disabled() {
1327          global $CFG;
1328          $this->resetAfterTest(true);
1329          $CFG->disableuserimages = true;
1330  
1331          $this->setAdminUser();
1332          $this->expectException('moodle_exception');
1333          core_user_external::update_picture(0);
1334      }
1335  
1336      /**
1337       * Test set_user_preferences
1338       */
1339      public function test_set_user_preferences_save() {
1340          global $DB;
1341          $this->resetAfterTest(true);
1342  
1343          $user1 = self::getDataGenerator()->create_user();
1344          $user2 = self::getDataGenerator()->create_user();
1345  
1346          // Save users preferences.
1347          $this->setAdminUser();
1348          $preferences = array(
1349              array(
1350                  'name' => 'htmleditor',
1351                  'value' => 'atto',
1352                  'userid' => $user1->id,
1353              ),
1354              array(
1355                  'name' => 'htmleditor',
1356                  'value' => 'tiny',
1357                  'userid' => $user2->id,
1358              )
1359          );
1360  
1361          $result = core_user_external::set_user_preferences($preferences);
1362          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1363          $this->assertCount(0, $result['warnings']);
1364          $this->assertCount(2, $result['saved']);
1365  
1366          // Get preference from DB to avoid cache.
1367          $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1368              array('userid' => $user1->id, 'name' => 'htmleditor')));
1369          $this->assertEquals('tiny', $DB->get_field('user_preferences', 'value',
1370              array('userid' => $user2->id, 'name' => 'htmleditor')));
1371      }
1372  
1373      /**
1374       * Test set_user_preferences
1375       */
1376      public function test_set_user_preferences_save_invalid_pref() {
1377          global $DB;
1378          $this->resetAfterTest(true);
1379  
1380          $user1 = self::getDataGenerator()->create_user();
1381  
1382          // Save users preferences.
1383          $this->setAdminUser();
1384          $preferences = array(
1385              array(
1386                  'name' => 'some_random_pref',
1387                  'value' => 'abc',
1388                  'userid' => $user1->id,
1389              ),
1390          );
1391  
1392          $result = core_user_external::set_user_preferences($preferences);
1393          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1394          $this->assertCount(1, $result['warnings']);
1395          $this->assertCount(0, $result['saved']);
1396          $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1397  
1398          // Nothing was written to DB.
1399          $this->assertEmpty($DB->count_records('user_preferences', array('name' => 'some_random_pref')));
1400      }
1401  
1402      /**
1403       * Test set_user_preferences for an invalid user
1404       */
1405      public function test_set_user_preferences_invalid_user() {
1406          $this->resetAfterTest(true);
1407  
1408          $this->setAdminUser();
1409          $preferences = array(
1410              array(
1411                  'name' => 'calendar_maxevents',
1412                  'value' => 4,
1413                  'userid' => -2
1414              )
1415          );
1416  
1417          $result = core_user_external::set_user_preferences($preferences);
1418          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1419          $this->assertCount(1, $result['warnings']);
1420          $this->assertCount(0, $result['saved']);
1421          $this->assertEquals('invaliduser', $result['warnings'][0]['warningcode']);
1422          $this->assertEquals(-2, $result['warnings'][0]['itemid']);
1423      }
1424  
1425      /**
1426       * Test set_user_preferences using an invalid preference
1427       */
1428      public function test_set_user_preferences_invalid_preference() {
1429          global $USER, $DB;
1430  
1431          $this->resetAfterTest(true);
1432          // Create a very long value.
1433          $this->setAdminUser();
1434          $preferences = array(
1435              array(
1436                  'name' => 'calendar_maxevents',
1437                  'value' => str_repeat('a', 1334),
1438                  'userid' => $USER->id
1439              )
1440          );
1441  
1442          $result = core_user_external::set_user_preferences($preferences);
1443          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1444          $this->assertCount(0, $result['warnings']);
1445          $this->assertCount(1, $result['saved']);
1446          // Cleaned valud of the preference was saved.
1447          $this->assertEquals(1, $DB->get_field('user_preferences', 'value',
1448              array('userid' => $USER->id, 'name' => 'calendar_maxevents')));
1449      }
1450  
1451      /**
1452       * Test set_user_preferences for other user not being admin
1453       */
1454      public function test_set_user_preferences_capability() {
1455          $this->resetAfterTest(true);
1456  
1457          $user1 = self::getDataGenerator()->create_user();
1458          $user2 = self::getDataGenerator()->create_user();
1459  
1460          $this->setUser($user1);
1461          $preferences = array(
1462              array(
1463                  'name' => 'calendar_maxevents',
1464                  'value' => 4,
1465                  'userid' => $user2->id
1466              )
1467          );
1468  
1469          $result = core_user_external::set_user_preferences($preferences);
1470  
1471          $this->assertCount(1, $result['warnings']);
1472          $this->assertCount(0, $result['saved']);
1473          $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1474          $this->assertEquals($user2->id, $result['warnings'][0]['itemid']);
1475      }
1476  
1477      /**
1478       * Test update_user_preferences unsetting an existing preference.
1479       */
1480      public function test_update_user_preferences_unset() {
1481          global $DB;
1482          $this->resetAfterTest(true);
1483  
1484          $user = self::getDataGenerator()->create_user();
1485  
1486          // Save users preferences.
1487          $this->setAdminUser();
1488          $preferences = array(
1489              array(
1490                  'name' => 'htmleditor',
1491                  'value' => 'atto',
1492                  'userid' => $user->id,
1493              )
1494          );
1495  
1496          $result = core_user_external::set_user_preferences($preferences);
1497          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1498          $this->assertCount(0, $result['warnings']);
1499          $this->assertCount(1, $result['saved']);
1500  
1501          // Get preference from DB to avoid cache.
1502          $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1503              array('userid' => $user->id, 'name' => 'htmleditor')));
1504  
1505          // Now, unset.
1506          $result = core_user_external::update_user_preferences($user->id, null, array(array('type' => 'htmleditor')));
1507  
1508          $this->assertEquals(0, $DB->count_records('user_preferences', array('userid' => $user->id, 'name' => 'htmleditor')));
1509      }
1510  
1511      /**
1512       * Test agree_site_policy
1513       */
1514      public function test_agree_site_policy() {
1515          global $CFG, $DB, $USER;
1516          $this->resetAfterTest(true);
1517  
1518          $user = self::getDataGenerator()->create_user();
1519          $this->setUser($user);
1520  
1521          // Site policy not set.
1522          $result = core_user_external::agree_site_policy();
1523          $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1524          $this->assertFalse($result['status']);
1525          $this->assertCount(1, $result['warnings']);
1526          $this->assertEquals('nositepolicy', $result['warnings'][0]['warningcode']);
1527  
1528          // Set a policy issue.
1529          $CFG->sitepolicy = 'https://moodle.org';
1530          $this->assertEquals(0, $USER->policyagreed);
1531  
1532          $result = core_user_external::agree_site_policy();
1533          $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1534          $this->assertTrue($result['status']);
1535          $this->assertCount(0, $result['warnings']);
1536          $this->assertEquals(1, $USER->policyagreed);
1537          $this->assertEquals(1, $DB->get_field('user', 'policyagreed', array('id' => $USER->id)));
1538  
1539          // Try again, we should get a warning.
1540          $result = core_user_external::agree_site_policy();
1541          $result = external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1542          $this->assertFalse($result['status']);
1543          $this->assertCount(1, $result['warnings']);
1544          $this->assertEquals('alreadyagreed', $result['warnings'][0]['warningcode']);
1545  
1546          // Set something to make require_login throws an exception.
1547          $otheruser = self::getDataGenerator()->create_user();
1548          $this->setUser($otheruser);
1549  
1550          $DB->set_field('user', 'lastname', '', array('id' => $USER->id));
1551          $USER->lastname = '';
1552          try {
1553              $result = core_user_external::agree_site_policy();
1554              $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown');
1555          } catch (\moodle_exception $e) {
1556              $this->assertEquals('usernotfullysetup', $e->errorcode);
1557          } catch (\Exception $e) {
1558              $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown.');
1559          }
1560      }
1561  
1562      /**
1563       * Test get_private_files_info
1564       */
1565      public function test_get_private_files_info() {
1566  
1567          $this->resetAfterTest(true);
1568          $user = self::getDataGenerator()->create_user();
1569          $this->setUser($user);
1570          $usercontext = \context_user::instance($user->id);
1571  
1572          $filerecord = array(
1573              'contextid' => $usercontext->id,
1574              'component' => 'user',
1575              'filearea'  => 'private',
1576              'itemid'    => 0,
1577              'filepath'  => '/',
1578              'filename'  => 'thefile',
1579          );
1580  
1581          $fs = get_file_storage();
1582          $file = $fs->create_file_from_string($filerecord, 'abc');
1583  
1584          // Get my private files information.
1585          $result = core_user_external::get_private_files_info();
1586          $result = external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1587          $this->assertEquals(1, $result['filecount']);
1588          $this->assertEquals($file->get_filesize(), $result['filesize']);
1589          $this->assertEquals(0, $result['foldercount']);
1590          $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1591  
1592          // As admin, get user information.
1593          $this->setAdminUser();
1594          $result = core_user_external::get_private_files_info($user->id);
1595          $result = external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1596          $this->assertEquals(1, $result['filecount']);
1597          $this->assertEquals($file->get_filesize(), $result['filesize']);
1598          $this->assertEquals(0, $result['foldercount']);
1599          $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1600      }
1601  
1602      /**
1603       * Test get_private_files_info missing permissions.
1604       */
1605      public function test_get_private_files_info_missing_permissions() {
1606  
1607          $this->resetAfterTest(true);
1608          $user1 = self::getDataGenerator()->create_user();
1609          $user2 = self::getDataGenerator()->create_user();
1610          $this->setUser($user1);
1611  
1612          $this->expectException('required_capability_exception');
1613          // Try to retrieve other user private files info.
1614          core_user_external::get_private_files_info($user2->id);
1615      }
1616  
1617      /**
1618       * Test the functionality of the {@see \core_user\external\search_identity} class.
1619       */
1620      public function test_external_search_identity() {
1621          global $CFG;
1622  
1623          $this->resetAfterTest(true);
1624          $this->setAdminUser();
1625  
1626          $user1 = self::getDataGenerator()->create_user([
1627              'firstname' => 'Firstone',
1628              'lastname' => 'Lastone',
1629              'username' => 'usernameone',
1630              'idnumber' => 'idnumberone',
1631              'email' => 'userone@example.com',
1632              'phone1' => 'tel1',
1633              'phone2' => 'tel2',
1634              'department' => 'Department Foo',
1635              'institution' => 'Institution Foo',
1636              'city' => 'City One',
1637              'country' => 'AU',
1638          ]);
1639  
1640          $user2 = self::getDataGenerator()->create_user([
1641              'firstname' => 'Firsttwo',
1642              'lastname' => 'Lasttwo',
1643              'username' => 'usernametwo',
1644              'idnumber' => 'idnumbertwo',
1645              'email' => 'usertwo@example.com',
1646              'phone1' => 'tel1',
1647              'phone2' => 'tel2',
1648              'department' => 'Department Foo',
1649              'institution' => 'Institution Foo',
1650              'city' => 'City One',
1651              'country' => 'AU',
1652          ]);
1653  
1654          $user3 = self::getDataGenerator()->create_user([
1655              'firstname' => 'Firstthree',
1656              'lastname' => 'Lastthree',
1657              'username' => 'usernamethree',
1658              'idnumber' => 'idnumberthree',
1659              'email' => 'userthree@example.com',
1660              'phone1' => 'tel1',
1661              'phone2' => 'tel2',
1662              'department' => 'Department Foo',
1663              'institution' => 'Institution Foo',
1664              'city' => 'City One',
1665              'country' => 'AU',
1666          ]);
1667  
1668          $CFG->showuseridentity = 'email,idnumber,city';
1669          $CFG->maxusersperpage = 3;
1670  
1671          $result = \core_user\external\search_identity::execute('Lastt');
1672          $result = external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1673  
1674          $this->assertEquals(2, count($result['list']));
1675          $this->assertEquals(3, $result['maxusersperpage']);
1676          $this->assertEquals(false, $result['overflow']);
1677  
1678          foreach ($result['list'] as $user) {
1679              $this->assertEquals(3, count($user['extrafields']));
1680              $this->assertEquals('email', $user['extrafields'][0]['name']);
1681              $this->assertEquals('idnumber', $user['extrafields'][1]['name']);
1682              $this->assertEquals('city', $user['extrafields'][2]['name']);
1683          }
1684  
1685          $CFG->showuseridentity = 'username';
1686          $CFG->maxusersperpage = 2;
1687  
1688          $result = \core_user\external\search_identity::execute('Firstt');
1689          $result = external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1690  
1691          $this->assertEquals(2, count($result['list']));
1692          $this->assertEquals(2, $result['maxusersperpage']);
1693          $this->assertEquals(false, $result['overflow']);
1694  
1695          foreach ($result['list'] as $user) {
1696              $this->assertEquals(1, count($user['extrafields']));
1697              $this->assertEquals('username', $user['extrafields'][0]['name']);
1698          }
1699  
1700          $CFG->showuseridentity = 'email';
1701          $CFG->maxusersperpage = 2;
1702  
1703          $result = \core_user\external\search_identity::execute('City One');
1704          $result = external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1705  
1706          $this->assertEquals(0, count($result['list']));
1707          $this->assertEquals(2, $result['maxusersperpage']);
1708          $this->assertEquals(false, $result['overflow']);
1709  
1710          $CFG->showuseridentity = 'city';
1711          $CFG->maxusersperpage = 2;
1712  
1713          foreach ($result['list'] as $user) {
1714              $this->assertEquals(1, count($user['extrafields']));
1715              $this->assertEquals('username', $user['extrafields'][0]['name']);
1716          }
1717  
1718          $result = \core_user\external\search_identity::execute('City One');
1719          $result = external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1720  
1721          $this->assertEquals(2, count($result['list']));
1722          $this->assertEquals(2, $result['maxusersperpage']);
1723          $this->assertEquals(true, $result['overflow']);
1724      }
1725  
1726      /**
1727       * Test functionality of the {@see \core_user\external\search_identity} class with alternativefullnameformat defined.
1728       */
1729      public function test_external_search_identity_with_alternativefullnameformat() {
1730          global $CFG;
1731  
1732          $this->resetAfterTest(true);
1733          $this->setAdminUser();
1734  
1735          $user1 = self::getDataGenerator()->create_user([
1736              'lastname' => '小柳',
1737              'lastnamephonetic' => 'Koyanagi',
1738              'firstname' => '秋',
1739              'firstnamephonetic' => 'Aki',
1740              'email' => 'koyanagiaki@example.com',
1741              'country' => 'JP',
1742          ]);
1743  
1744          $CFG->showuseridentity = 'email';
1745          $CFG->maxusersperpage = 3;
1746          $CFG->alternativefullnameformat =
1747              '<ruby>lastname firstname <rp>(</rp><rt>lastnamephonetic firstnamephonetic</rt><rp>)</rp></ruby>';
1748  
1749          $result = \core_user\external\search_identity::execute('Ak');
1750          $result = external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1751  
1752          $this->assertEquals(1, count($result['list']));
1753          $this->assertEquals(3, $result['maxusersperpage']);
1754          $this->assertEquals(false, $result['overflow']);
1755  
1756          foreach ($result['list'] as $user) {
1757              $this->assertEquals(1, count($user['extrafields']));
1758              $this->assertEquals('email', $user['extrafields'][0]['name']);
1759          }
1760      }
1761  
1762      /**
1763       * Test verifying that update_user_preferences prevents changes to the default homepage for other users.
1764       */
1765      public function test_update_user_preferences_homepage_permission_callback() {
1766          global $DB;
1767          $this->resetAfterTest();
1768  
1769          $user = self::getDataGenerator()->create_user();
1770          $this->setUser($user);
1771          $adminuser = get_admin();
1772  
1773          // Allow user selection of the default homepage via preferences.
1774          set_config('defaulthomepage', HOMEPAGE_USER);
1775  
1776          // Try to save another user's home page preference which uses the permissioncallback.
1777          $preferences = [
1778              [
1779                  'name' => 'user_home_page_preference',
1780                  'value' => '3',
1781                  'userid' => $adminuser->id,
1782              ]
1783          ];
1784          $result = core_user_external::set_user_preferences($preferences);
1785          $result = external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1786          $this->assertCount(1, $result['warnings']);
1787          $this->assertCount(0, $result['saved']);
1788  
1789          // Verify no change to the preference, checking from DB to avoid cache.
1790          $this->assertEquals(null, $DB->get_field('user_preferences', 'value',
1791              ['userid' => $adminuser->id, 'name' => 'user_home_page_preference']));
1792      }
1793  }