Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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

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