Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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          return $return;
 360      }
 361  
 362      /**
 363       * Test get_course_user_profiles
 364       */
 365      public function test_get_course_user_profiles() {
 366          global $USER, $CFG;
 367  
 368          $this->resetAfterTest(true);
 369  
 370          $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
 371  
 372          // Call the external function.
 373          $enrolledusers = core_user_external::get_course_user_profiles(array(
 374                      array('userid' => $USER->id, 'courseid' => $data->course->id)));
 375  
 376          // We need to execute the return values cleaning process to simulate the web service server.
 377          $enrolledusers = \external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
 378  
 379          // Check we retrieve the good total number of enrolled users + no error on capability.
 380          $this->assertEquals(1, count($enrolledusers));
 381      }
 382  
 383      public function test_get_user_course_profile_as_admin() {
 384          global $USER, $CFG;
 385  
 386          global $USER, $CFG;
 387  
 388          $this->resetAfterTest(true);
 389  
 390          $data = $this->get_course_user_profiles_setup('moodle/user:viewdetails');
 391  
 392          // Do the same call as admin to receive all possible fields.
 393          $this->setAdminUser();
 394          $USER->email = "admin@example.com";
 395  
 396          // Call the external function.
 397          $enrolledusers = core_user_external::get_course_user_profiles(array(
 398              array('userid' => $data->user1->id, 'courseid' => $data->course->id)));
 399  
 400          // We need to execute the return values cleaning process to simulate the web service server.
 401          $enrolledusers = \external_api::clean_returnvalue(core_user_external::get_course_user_profiles_returns(), $enrolledusers);
 402  
 403          foreach($enrolledusers as $enrolleduser) {
 404              if ($enrolleduser['username'] == $data->user1->username) {
 405                  $this->assertEquals($data->user1->idnumber, $enrolleduser['idnumber']);
 406                  $this->assertEquals($data->user1->firstname, $enrolleduser['firstname']);
 407                  $this->assertEquals($data->user1->lastname, $enrolleduser['lastname']);
 408                  $this->assertEquals($data->user1->email, $enrolleduser['email']);
 409                  $this->assertEquals($data->user1->address, $enrolleduser['address']);
 410                  $this->assertEquals($data->user1->phone1, $enrolleduser['phone1']);
 411                  $this->assertEquals($data->user1->phone2, $enrolleduser['phone2']);
 412                  $this->assertEquals($data->user1->department, $enrolleduser['department']);
 413                  $this->assertEquals($data->user1->institution, $enrolleduser['institution']);
 414                  $this->assertEquals($data->user1->description, $enrolleduser['description']);
 415                  $this->assertEquals(FORMAT_HTML, $enrolleduser['descriptionformat']);
 416                  $this->assertEquals($data->user1->city, $enrolleduser['city']);
 417                  $this->assertEquals($data->user1->country, $enrolleduser['country']);
 418                  if (!empty($CFG->usetags)) {
 419                      $this->assertEquals(implode(', ', $data->user1->interests), $enrolleduser['interests']);
 420                  }
 421              }
 422          }
 423      }
 424  
 425      /**
 426       * Test create_users
 427       */
 428      public function test_create_users() {
 429          global $DB;
 430  
 431          $this->resetAfterTest(true);
 432  
 433          $user1 = array(
 434              'username' => 'usernametest1',
 435              'password' => 'Moodle2012!',
 436              'idnumber' => 'idnumbertest1',
 437              'firstname' => 'First Name User Test 1',
 438              'lastname' => 'Last Name User Test 1',
 439              'middlename' => 'Middle Name User Test 1',
 440              'lastnamephonetic' => '最後のお名前のテスト一号',
 441              'firstnamephonetic' => 'お名前のテスト一号',
 442              'alternatename' => 'Alternate Name User Test 1',
 443              'email' => 'usertest1@example.com',
 444              'description' => 'This is a description for user 1',
 445              'city' => 'Perth',
 446              'country' => 'AU',
 447              'preferences' => [[
 448                      'type' => 'htmleditor',
 449                      'value' => 'atto'
 450                  ], [
 451                      'type' => 'invalidpreference',
 452                      'value' => 'abcd'
 453                  ]
 454              ],
 455              'department' => 'College of Science',
 456              'institution' => 'National Institute of Physics',
 457              'phone1' => '01 2345 6789',
 458              'maildisplay' => 1,
 459              'interests' => 'badminton, basketball, cooking,  '
 460          );
 461  
 462          // User with an authentication method done externally.
 463          $user2 = array(
 464              'username' => 'usernametest2',
 465              'firstname' => 'First Name User Test 2',
 466              'lastname' => 'Last Name User Test 2',
 467              'email' => 'usertest2@example.com',
 468              'auth' => 'oauth2'
 469          );
 470  
 471          $context = \context_system::instance();
 472          $roleid = $this->assignUserCapability('moodle/user:create', $context->id);
 473          $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
 474  
 475          // Call the external function.
 476          $createdusers = core_user_external::create_users(array($user1, $user2));
 477  
 478          // We need to execute the return values cleaning process to simulate the web service server.
 479          $createdusers = \external_api::clean_returnvalue(core_user_external::create_users_returns(), $createdusers);
 480  
 481          // Check we retrieve the good total number of created users + no error on capability.
 482          $this->assertCount(2, $createdusers);
 483  
 484          foreach($createdusers as $createduser) {
 485              $dbuser = $DB->get_record('user', array('id' => $createduser['id']));
 486  
 487              if ($createduser['username'] === $user1['username']) {
 488                  $usertotest = $user1;
 489                  $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
 490                  $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
 491                  // Confirm user interests have been saved.
 492                  $interests = \core_tag_tag::get_item_tags_array('core', 'user', $createduser['id'],
 493                          \core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
 494                  // There should be 3 user interests.
 495                  $this->assertCount(3, $interests);
 496  
 497              } else if ($createduser['username'] === $user2['username']) {
 498                  $usertotest = $user2;
 499              }
 500  
 501              foreach ($dbuser as $property => $value) {
 502                  if ($property === 'password') {
 503                      if ($usertotest === $user2) {
 504                          // External auth mechanisms don't store password in the user table.
 505                          $this->assertEquals(AUTH_PASSWORD_NOT_CACHED, $value);
 506                      } else {
 507                          // Skip hashed passwords.
 508                          continue;
 509                      }
 510                  }
 511                  // Confirm that the values match.
 512                  if (isset($usertotest[$property])) {
 513                      $this->assertEquals($usertotest[$property], $value);
 514                  }
 515              }
 516          }
 517  
 518          // Call without required capability
 519          $this->unassignUserCapability('moodle/user:create', $context->id, $roleid);
 520          $this->expectException('required_capability_exception');
 521          core_user_external::create_users(array($user1));
 522      }
 523  
 524      /**
 525       * Test create_users with password and createpassword parameter not set.
 526       */
 527      public function test_create_users_empty_password() {
 528          $this->resetAfterTest();
 529          $this->setAdminUser();
 530  
 531          $user = [
 532              'username' => 'usernametest1',
 533              'firstname' => 'First Name User Test 1',
 534              'lastname' => 'Last Name User Test 1',
 535              'email' => 'usertest1@example.com',
 536          ];
 537  
 538          // This should throw an exception because either password or createpassword param must be passed for auth_manual.
 539          $this->expectException(\invalid_parameter_exception::class);
 540          core_user_external::create_users([$user]);
 541      }
 542  
 543      /**
 544       * Data provider for \core_user_externallib_testcase::test_create_users_with_same_emails().
 545       */
 546      public function create_users_provider_with_same_emails() {
 547          return [
 548              'Same emails allowed, same case' => [
 549                  1, false
 550              ],
 551              'Same emails allowed, different case' => [
 552                  1, true
 553              ],
 554              'Same emails disallowed, same case' => [
 555                  0, false
 556              ],
 557              'Same emails disallowed, different case' => [
 558                  0, true
 559              ],
 560          ];
 561      }
 562  
 563      /**
 564       * Test for \core_user_external::create_users() when user using the same email addresses are being created.
 565       *
 566       * @dataProvider create_users_provider_with_same_emails
 567       * @param int $sameemailallowed The value to set for $CFG->allowaccountssameemail.
 568       * @param boolean $differentcase Whether to user a different case for the other user.
 569       */
 570      public function test_create_users_with_same_emails($sameemailallowed, $differentcase) {
 571          global $DB;
 572  
 573          $this->resetAfterTest();
 574          $this->setAdminUser();
 575  
 576          // Allow multiple users with the same email address.
 577          set_config('allowaccountssameemail', $sameemailallowed);
 578          $users = [
 579              [
 580                  'username' => 's1',
 581                  'firstname' => 'Johnny',
 582                  'lastname' => 'Bravo',
 583                  'email' => 's1@example.com',
 584                  'password' => 'Passw0rd!'
 585              ],
 586              [
 587                  'username' => 's2',
 588                  'firstname' => 'John',
 589                  'lastname' => 'Doe',
 590                  'email' => $differentcase ? 'S1@EXAMPLE.COM' : 's1@example.com',
 591                  'password' => 'Passw0rd!'
 592              ],
 593          ];
 594  
 595          if (!$sameemailallowed) {
 596              // This should throw an exception when $CFG->allowaccountssameemail is empty.
 597              $this->expectException(\invalid_parameter_exception::class);
 598          }
 599  
 600          // Create our users.
 601          core_user_external::create_users($users);
 602  
 603          // Confirm that the users have been created.
 604          list($insql, $params) = $DB->get_in_or_equal(['s1', 's2']);
 605          $this->assertEquals(2, $DB->count_records_select('user', 'username ' . $insql, $params));
 606      }
 607  
 608      /**
 609       * Test create_users with invalid parameters
 610       *
 611       * @dataProvider data_create_users_invalid_parameter
 612       * @param array $data User data to attempt to register.
 613       * @param string $expectmessage Expected exception message.
 614       */
 615      public function test_create_users_invalid_parameter(array $data, $expectmessage) {
 616          global $USER, $CFG, $DB;
 617  
 618          $this->resetAfterTest(true);
 619          $this->assignUserCapability('moodle/user:create', SYSCONTEXTID);
 620  
 621          $this->expectException('invalid_parameter_exception');
 622          $this->expectExceptionMessage($expectmessage);
 623  
 624          core_user_external::create_users(array($data));
 625      }
 626  
 627      /**
 628       * Data provider for {@see self::test_create_users_invalid_parameter()}.
 629       *
 630       * @return array
 631       */
 632      public function data_create_users_invalid_parameter() {
 633          return [
 634              'blank_username' => [
 635                  'data' => [
 636                      'username' => '',
 637                      'firstname' => 'Foo',
 638                      'lastname' => 'Bar',
 639                      'email' => 'foobar@example.com',
 640                      'createpassword' => 1,
 641                  ],
 642                  'expectmessage' => 'The field username cannot be blank',
 643              ],
 644              'blank_firtname' => [
 645                  'data' => [
 646                      'username' => 'foobar',
 647                      'firstname' => "\t \n",
 648                      'lastname' => 'Bar',
 649                      'email' => 'foobar@example.com',
 650                      'createpassword' => 1,
 651                  ],
 652                  'expectmessage' => 'The field firstname cannot be blank',
 653              ],
 654              'blank_lastname' => [
 655                  'data' => [
 656                      'username' => 'foobar',
 657                      'firstname' => '0',
 658                      'lastname' => '   ',
 659                      'email' => 'foobar@example.com',
 660                      'createpassword' => 1,
 661                  ],
 662                  'expectmessage' => 'The field lastname cannot be blank',
 663              ],
 664              'invalid_email' => [
 665                  'data' => [
 666                      'username' => 'foobar',
 667                      'firstname' => 'Foo',
 668                      'lastname' => 'Bar',
 669                      'email' => '@foobar',
 670                      'createpassword' => 1,
 671                  ],
 672                  'expectmessage' => 'Email address is invalid',
 673              ],
 674              'missing_password' => [
 675                  'data' => [
 676                      'username' => 'foobar',
 677                      'firstname' => 'Foo',
 678                      'lastname' => 'Bar',
 679                      'email' => 'foobar@example.com',
 680                  ],
 681                  'expectmessage' => 'Invalid password: you must provide a password, or set createpassword',
 682              ],
 683          ];
 684      }
 685  
 686      /**
 687       * Test delete_users
 688       */
 689      public function test_delete_users() {
 690          global $USER, $CFG, $DB;
 691  
 692          $this->resetAfterTest(true);
 693  
 694          $user1 = self::getDataGenerator()->create_user();
 695          $user2 = self::getDataGenerator()->create_user();
 696  
 697          // Check the users were correctly created.
 698          $this->assertEquals(2, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
 699                  array('userid1' => $user1->id, 'userid2' => $user2->id)));
 700  
 701          $context = \context_system::instance();
 702          $roleid = $this->assignUserCapability('moodle/user:delete', $context->id);
 703  
 704          // Call the external function.
 705          core_user_external::delete_users(array($user1->id, $user2->id));
 706  
 707          // Check we retrieve no users + no error on capability.
 708          $this->assertEquals(0, $DB->count_records_select('user', 'deleted = 0 AND (id = :userid1 OR id = :userid2)',
 709                  array('userid1' => $user1->id, 'userid2' => $user2->id)));
 710  
 711          // Call without required capability.
 712          $this->unassignUserCapability('moodle/user:delete', $context->id, $roleid);
 713          $this->expectException('required_capability_exception');
 714          core_user_external::delete_users(array($user1->id, $user2->id));
 715      }
 716  
 717      /**
 718       * Test update_users
 719       */
 720      public function test_update_users() {
 721          global $USER, $CFG, $DB;
 722  
 723          $this->resetAfterTest(true);
 724  
 725          $wsuser = self::getDataGenerator()->create_user();
 726          self::setUser($wsuser);
 727  
 728          $context = \context_user::instance($USER->id);
 729          $contextid = $context->id;
 730          $filename = "reddot.png";
 731          $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
 732              . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 733  
 734          // Call the files api to create a file.
 735          $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/',
 736                  $filename, $filecontent, null, null);
 737          $draftfile = \external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
 738  
 739          $draftid = $draftfile['itemid'];
 740  
 741          $user1 = self::getDataGenerator()->create_user();
 742  
 743          $user1 = array(
 744              'id' => $user1->id,
 745              'username' => 'usernametest1',
 746              'password' => 'Moodle2012!',
 747              'idnumber' => 'idnumbertest1',
 748              'firstname' => 'First Name User Test 1',
 749              'lastname' => 'Last Name User Test 1',
 750              'middlename' => 'Middle Name User Test 1',
 751              'lastnamephonetic' => '最後のお名前のテスト一号',
 752              'firstnamephonetic' => 'お名前のテスト一号',
 753              'alternatename' => 'Alternate Name User Test 1',
 754              'email' => 'usertest1@example.com',
 755              'description' => 'This is a description for user 1',
 756              'city' => 'Perth',
 757              'userpicture' => $draftid,
 758              'country' => 'AU',
 759              'preferences' => [[
 760                      'type' => 'htmleditor',
 761                      'value' => 'atto'
 762                  ], [
 763                      'type' => 'invialidpreference',
 764                      'value' => 'abcd'
 765                  ]
 766              ],
 767              'department' => 'College of Science',
 768              'institution' => 'National Institute of Physics',
 769              'phone1' => '01 2345 6789',
 770              'maildisplay' => 1,
 771              'interests' => 'badminton, basketball, cooking,  '
 772          );
 773  
 774          $context = \context_system::instance();
 775          $roleid = $this->assignUserCapability('moodle/user:update', $context->id);
 776          $this->assignUserCapability('moodle/user:editprofile', $context->id, $roleid);
 777  
 778          // Check we can't update deleted users, guest users, site admin.
 779          $user2 = $user3 = $user4 = $user1;
 780          $user2['id'] = $CFG->siteguest;
 781  
 782          $siteadmins = explode(',', $CFG->siteadmins);
 783          $user3['id'] = array_shift($siteadmins);
 784  
 785          $userdeleted = self::getDataGenerator()->create_user();
 786          $user4['id'] = $userdeleted->id;
 787          user_delete_user($userdeleted);
 788  
 789          // Call the external function.
 790          core_user_external::update_users(array($user1, $user2, $user3, $user4));
 791  
 792          $dbuser2 = $DB->get_record('user', array('id' => $user2['id']));
 793          $this->assertNotEquals($dbuser2->username, $user2['username']);
 794          $dbuser3 = $DB->get_record('user', array('id' => $user3['id']));
 795          $this->assertNotEquals($dbuser3->username, $user3['username']);
 796          $dbuser4 = $DB->get_record('user', array('id' => $user4['id']));
 797          $this->assertNotEquals($dbuser4->username, $user4['username']);
 798  
 799          $dbuser = $DB->get_record('user', array('id' => $user1['id']));
 800          $this->assertEquals($dbuser->username, $user1['username']);
 801          $this->assertEquals($dbuser->idnumber, $user1['idnumber']);
 802          $this->assertEquals($dbuser->firstname, $user1['firstname']);
 803          $this->assertEquals($dbuser->lastname, $user1['lastname']);
 804          $this->assertEquals($dbuser->email, $user1['email']);
 805          $this->assertEquals($dbuser->description, $user1['description']);
 806          $this->assertEquals($dbuser->city, $user1['city']);
 807          $this->assertEquals($dbuser->country, $user1['country']);
 808          $this->assertNotEquals(0, $dbuser->picture, 'Picture must be set to the new icon itemid for this user');
 809          $this->assertEquals($dbuser->department, $user1['department']);
 810          $this->assertEquals($dbuser->institution, $user1['institution']);
 811          $this->assertEquals($dbuser->phone1, $user1['phone1']);
 812          $this->assertEquals($dbuser->maildisplay, $user1['maildisplay']);
 813          $this->assertEquals('atto', get_user_preferences('htmleditor', null, $dbuser));
 814          $this->assertEquals(null, get_user_preferences('invalidpreference', null, $dbuser));
 815  
 816          // Confirm user interests have been saved.
 817          $interests = \core_tag_tag::get_item_tags_array('core', 'user', $user1['id'], \core_tag_tag::BOTH_STANDARD_AND_NOT, 0, false);
 818          // There should be 3 user interests.
 819          $this->assertCount(3, $interests);
 820  
 821          // Confirm no picture change when parameter is not supplied.
 822          unset($user1['userpicture']);
 823          core_user_external::update_users(array($user1));
 824          $dbusernopic = $DB->get_record('user', array('id' => $user1['id']));
 825          $this->assertEquals($dbuser->picture, $dbusernopic->picture, 'Picture not change without the parameter.');
 826  
 827          // Confirm delete of picture deletes the picture from the user record.
 828          $user1['userpicture'] = 0;
 829          core_user_external::update_users(array($user1));
 830          $dbuserdelpic = $DB->get_record('user', array('id' => $user1['id']));
 831          $this->assertEquals(0, $dbuserdelpic->picture, 'Picture must be deleted when sent as 0.');
 832  
 833  
 834          // Call without required capability.
 835          $this->unassignUserCapability('moodle/user:update', $context->id, $roleid);
 836          $this->expectException('required_capability_exception');
 837          core_user_external::update_users(array($user1));
 838      }
 839  
 840      /**
 841       * Data provider for testing \core_user_external::update_users() for users with same emails
 842       *
 843       * @return array
 844       */
 845      public function users_with_same_emails() {
 846          return [
 847              'Same emails not allowed: Update name using exactly the same email' => [
 848                  0, 'John', 's1@example.com', 'Johnny', 's1@example.com', false, true
 849              ],
 850              'Same emails not allowed: Update using someone else\'s email' => [
 851                  0, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, false
 852              ],
 853              'Same emails allowed: Update using someone else\'s email' => [
 854                  1, 'John', 's1@example.com', 'Johnny', 's2@example.com', true, true
 855              ],
 856              'Same emails not allowed: Update using same email but with different case' => [
 857                  0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', false, true
 858              ],
 859              'Same emails not allowed: Update using another user\'s email similar to user but with different case' => [
 860                  0, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, false
 861              ],
 862              'Same emails allowed: Update using another user\'s email similar to user but with different case' => [
 863                  1, 'John', 's1@example.com', 'Johnny', 'S1@EXAMPLE.COM', true, true
 864              ],
 865          ];
 866      }
 867  
 868      /**
 869       * Test update_users using similar emails with varying cases.
 870       *
 871       * @dataProvider users_with_same_emails
 872       * @param boolean $allowsameemail The value to set for $CFG->allowaccountssameemail.
 873       * @param string $currentname The user's current name.
 874       * @param string $currentemail The user's current email.
 875       * @param string $newname The user's new name.
 876       * @param string $newemail The user's new email.
 877       * @param boolean $withanotheruser Whether to create another user that has the same email as the target user's new email.
 878       * @param boolean $successexpected Whether we expect that the target user's email/name will be updated.
 879       */
 880      public function test_update_users_emails_with_different_cases($allowsameemail, $currentname, $currentemail,
 881                                                                    $newname, $newemail, $withanotheruser, $successexpected) {
 882          global $DB;
 883  
 884          $this->resetAfterTest();
 885          $this->setAdminUser();
 886  
 887          // Set the value for $CFG->allowaccountssameemail.
 888          set_config('allowaccountssameemail', $allowsameemail);
 889  
 890          $generator = self::getDataGenerator();
 891  
 892          // Create the user that we wish to update.
 893          $usertoupdate = $generator->create_user(['email' => $currentemail, 'firstname' => $currentname]);
 894  
 895          if ($withanotheruser) {
 896              // Create another user that has the same email as the new email that we'd like to update for our target user.
 897              $generator->create_user(['email' => $newemail]);
 898          }
 899  
 900          // Build the user update parameters.
 901          $updateparams = [
 902              'id' => $usertoupdate->id,
 903              'email' => $newemail,
 904              'firstname' => $newname
 905          ];
 906          // Let's try to update the user's information.
 907          core_user_external::update_users([$updateparams]);
 908  
 909          // Fetch the updated user record.
 910          $userrecord = $DB->get_record('user', ['id' => $usertoupdate->id], 'id, email, firstname');
 911  
 912          // If we expect the update to succeed, then the email/name would have been changed.
 913          if ($successexpected) {
 914              $expectedemail = $newemail;
 915              $expectedname = $newname;
 916          } else {
 917              $expectedemail = $currentemail;
 918              $expectedname = $currentname;
 919          }
 920          // Confirm that our expectations are met.
 921          $this->assertEquals($expectedemail, $userrecord->email);
 922          $this->assertEquals($expectedname, $userrecord->firstname);
 923      }
 924  
 925      /**
 926       * Test add_user_private_files
 927       */
 928      public function test_add_user_private_files() {
 929          global $USER, $CFG, $DB;
 930  
 931          $this->resetAfterTest(true);
 932  
 933          $context = \context_system::instance();
 934          $roleid = $this->assignUserCapability('moodle/user:manageownfiles', $context->id);
 935  
 936          $context = \context_user::instance($USER->id);
 937          $contextid = $context->id;
 938          $component = "user";
 939          $filearea = "draft";
 940          $itemid = 0;
 941          $filepath = "/";
 942          $filename = "Simple.txt";
 943          $filecontent = base64_encode("Let us create a nice simple file");
 944          $contextlevel = null;
 945          $instanceid = null;
 946          $browser = get_file_browser();
 947  
 948          // Call the files api to create a file.
 949          $draftfile = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
 950                                                   $filename, $filecontent, $contextlevel, $instanceid);
 951          $draftfile = \external_api::clean_returnvalue(core_files_external::upload_returns(), $draftfile);
 952  
 953          $draftid = $draftfile['itemid'];
 954          // Make sure the file was created.
 955          $file = $browser->get_file_info($context, $component, $filearea, $draftid, $filepath, $filename);
 956          $this->assertNotEmpty($file);
 957  
 958          // Make sure the file does not exist in the user private files.
 959          $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
 960          $this->assertEmpty($file);
 961  
 962          // Call the external function.
 963          core_user_external::add_user_private_files($draftid);
 964  
 965          // Make sure the file was added to the user private files.
 966          $file = $browser->get_file_info($context, $component, 'private', 0, $filepath, $filename);
 967          $this->assertNotEmpty($file);
 968      }
 969  
 970      /**
 971       * Test add user device
 972       */
 973      public function test_add_user_device() {
 974          global $USER, $CFG, $DB;
 975  
 976          $this->resetAfterTest(true);
 977  
 978          $device = array(
 979                  'appid' => 'com.moodle.moodlemobile',
 980                  'name' => 'occam',
 981                  'model' => 'Nexus 4',
 982                  'platform' => 'Android',
 983                  'version' => '4.2.2',
 984                  'pushid' => 'apushdkasdfj4835',
 985                  'uuid' => 'asdnfl348qlksfaasef859'
 986                  );
 987  
 988          // Call the external function.
 989          core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
 990                                              $device['version'], $device['pushid'], $device['uuid']);
 991  
 992          $created = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
 993          $created = (array) $created;
 994  
 995          $this->assertEquals($device, array_intersect_key((array)$created, $device));
 996  
 997          // Test reuse the same pushid value.
 998          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
 999                                                          $device['version'], $device['pushid'], $device['uuid']);
1000          // We need to execute the return values cleaning process to simulate the web service server.
1001          $warnings = \external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1002          $this->assertCount(1, $warnings);
1003  
1004          // Test update an existing device.
1005          $device['pushid'] = 'different than before';
1006          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1007                                                          $device['version'], $device['pushid'], $device['uuid']);
1008          $warnings = \external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1009  
1010          $this->assertEquals(1, $DB->count_records('user_devices'));
1011          $updated = $DB->get_record('user_devices', array('pushid' => $device['pushid']));
1012          $this->assertEquals($device, array_intersect_key((array)$updated, $device));
1013  
1014          // Test creating a new device just changing the uuid.
1015          $device['uuid'] = 'newuidforthesameuser';
1016          $device['pushid'] = 'new different than before';
1017          $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1018                                                          $device['version'], $device['pushid'], $device['uuid']);
1019          $warnings = \external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings);
1020          $this->assertEquals(2, $DB->count_records('user_devices'));
1021      }
1022  
1023      /**
1024       * Test remove user device
1025       */
1026      public function test_remove_user_device() {
1027          global $USER, $CFG, $DB;
1028  
1029          $this->resetAfterTest(true);
1030  
1031          $device = array(
1032                  'appid' => 'com.moodle.moodlemobile',
1033                  'name' => 'occam',
1034                  'model' => 'Nexus 4',
1035                  'platform' => 'Android',
1036                  'version' => '4.2.2',
1037                  'pushid' => 'apushdkasdfj4835',
1038                  'uuid' => 'ABCDE3723ksdfhasfaasef859'
1039                  );
1040  
1041          // A device with the same properties except the appid and pushid.
1042          $device2 = $device;
1043          $device2['pushid'] = "0987654321";
1044          $device2['appid'] = "other.app.com";
1045  
1046          $this->setAdminUser();
1047          // Create a user device using the external API function.
1048          core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'],
1049                                              $device['version'], $device['pushid'], $device['uuid']);
1050  
1051          // Create the same device but for a different app.
1052          core_user_external::add_user_device($device2['appid'], $device2['name'], $device2['model'], $device2['platform'],
1053                                              $device2['version'], $device2['pushid'], $device2['uuid']);
1054  
1055          // Try to remove a device that does not exist.
1056          $result = core_user_external::remove_user_device('1234567890');
1057          $result = \external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1058          $this->assertFalse($result['removed']);
1059          $this->assertCount(1, $result['warnings']);
1060  
1061          // Try to remove a device that does not exist for an existing app.
1062          $result = core_user_external::remove_user_device('1234567890', $device['appid']);
1063          $result = \external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1064          $this->assertFalse($result['removed']);
1065          $this->assertCount(1, $result['warnings']);
1066  
1067          // Remove an existing device for an existing app. This will remove one of the two devices.
1068          $result = core_user_external::remove_user_device($device['uuid'], $device['appid']);
1069          $result = \external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1070          $this->assertTrue($result['removed']);
1071  
1072          // Remove all the devices. This must remove the remaining device.
1073          $result = core_user_external::remove_user_device($device['uuid']);
1074          $result = \external_api::clean_returnvalue(core_user_external::remove_user_device_returns(), $result);
1075          $this->assertTrue($result['removed']);
1076      }
1077  
1078      /**
1079       * Test get_user_preferences
1080       */
1081      public function test_get_user_preferences() {
1082          $this->resetAfterTest(true);
1083  
1084          $user = self::getDataGenerator()->create_user();
1085          set_user_preference('calendar_maxevents', 1, $user);
1086          set_user_preference('some_random_text', 'text', $user);
1087  
1088          $this->setUser($user);
1089  
1090          $result = core_user_external::get_user_preferences();
1091          $result = \external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1092          $this->assertCount(0, $result['warnings']);
1093          // Expect 3, _lastloaded is always returned.
1094          $this->assertCount(3, $result['preferences']);
1095  
1096          foreach ($result['preferences'] as $pref) {
1097              if ($pref['name'] === '_lastloaded') {
1098                  continue;
1099              }
1100              // Check we receive the expected preferences.
1101              $this->assertEquals(get_user_preferences($pref['name']), $pref['value']);
1102          }
1103  
1104          // Retrieve just one preference.
1105          $result = core_user_external::get_user_preferences('some_random_text');
1106          $result = \external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1107          $this->assertCount(0, $result['warnings']);
1108          $this->assertCount(1, $result['preferences']);
1109          $this->assertEquals('text', $result['preferences'][0]['value']);
1110  
1111          // Retrieve non-existent preference.
1112          $result = core_user_external::get_user_preferences('non_existent');
1113          $result = \external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1114          $this->assertCount(0, $result['warnings']);
1115          $this->assertCount(1, $result['preferences']);
1116          $this->assertEquals(null, $result['preferences'][0]['value']);
1117  
1118          // Check that as admin we can retrieve all the preferences for any user.
1119          $this->setAdminUser();
1120          $result = core_user_external::get_user_preferences('', $user->id);
1121          $result = \external_api::clean_returnvalue(core_user_external::get_user_preferences_returns(), $result);
1122          $this->assertCount(0, $result['warnings']);
1123          $this->assertCount(3, $result['preferences']);
1124  
1125          foreach ($result['preferences'] as $pref) {
1126              if ($pref['name'] === '_lastloaded') {
1127                  continue;
1128              }
1129              // Check we receive the expected preferences.
1130              $this->assertEquals(get_user_preferences($pref['name'], null, $user), $pref['value']);
1131          }
1132  
1133          // Check that as a non admin user we cannot retrieve other users preferences.
1134          $anotheruser = self::getDataGenerator()->create_user();
1135          $this->setUser($anotheruser);
1136  
1137          $this->expectException('required_capability_exception');
1138          $result = core_user_external::get_user_preferences('', $user->id);
1139      }
1140  
1141      /**
1142       * Test update_picture
1143       */
1144      public function test_update_picture() {
1145          global $DB, $USER;
1146  
1147          $this->resetAfterTest(true);
1148  
1149          $user = self::getDataGenerator()->create_user();
1150          self::setUser($user);
1151  
1152          $context = \context_user::instance($USER->id);
1153          $contextid = $context->id;
1154          $filename = "reddot.png";
1155          $filecontent = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38"
1156              . "GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
1157  
1158          // Call the files api to create a file.
1159          $draftfile = core_files_external::upload($contextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1160          $draftid = $draftfile['itemid'];
1161  
1162          // Change user profile image.
1163          $result = core_user_external::update_picture($draftid);
1164          $result = \external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1165          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1166          // The new revision is in the url for the user.
1167          $this->assertStringContainsString($picture, $result['profileimageurl']);
1168          // Check expected URL for serving the image.
1169          $this->assertStringContainsString("/$contextid/user/icon", $result['profileimageurl']);
1170  
1171          // Delete image.
1172          $result = core_user_external::update_picture(0, true);
1173          $result = \external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1174          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1175          // No picture.
1176          $this->assertEquals(0, $picture);
1177  
1178          // Add again the user profile image (as admin).
1179          $this->setAdminUser();
1180  
1181          $context = \context_user::instance($USER->id);
1182          $admincontextid = $context->id;
1183          $draftfile = core_files_external::upload($admincontextid, 'user', 'draft', 0, '/', $filename, $filecontent, null, null);
1184          $draftid = $draftfile['itemid'];
1185  
1186          $result = core_user_external::update_picture($draftid, false, $user->id);
1187          $result = \external_api::clean_returnvalue(core_user_external::update_picture_returns(), $result);
1188          // The new revision is in the url for the user.
1189          $picture = $DB->get_field('user', 'picture', array('id' => $user->id));
1190          $this->assertStringContainsString($picture, $result['profileimageurl']);
1191          $this->assertStringContainsString("/$contextid/user/icon", $result['profileimageurl']);
1192      }
1193  
1194      /**
1195       * Test update_picture disabled
1196       */
1197      public function test_update_picture_disabled() {
1198          global $CFG;
1199          $this->resetAfterTest(true);
1200          $CFG->disableuserimages = true;
1201  
1202          $this->setAdminUser();
1203          $this->expectException('moodle_exception');
1204          core_user_external::update_picture(0);
1205      }
1206  
1207      /**
1208       * Test set_user_preferences
1209       */
1210      public function test_set_user_preferences_save() {
1211          global $DB;
1212          $this->resetAfterTest(true);
1213  
1214          $user1 = self::getDataGenerator()->create_user();
1215          $user2 = self::getDataGenerator()->create_user();
1216  
1217          // Save users preferences.
1218          $this->setAdminUser();
1219          $preferences = array(
1220              array(
1221                  'name' => 'htmleditor',
1222                  'value' => 'atto',
1223                  'userid' => $user1->id,
1224              ),
1225              array(
1226                  'name' => 'htmleditor',
1227                  'value' => 'tinymce',
1228                  'userid' => $user2->id,
1229              )
1230          );
1231  
1232          $result = core_user_external::set_user_preferences($preferences);
1233          $result = \external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1234          $this->assertCount(0, $result['warnings']);
1235          $this->assertCount(2, $result['saved']);
1236  
1237          // Get preference from DB to avoid cache.
1238          $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1239              array('userid' => $user1->id, 'name' => 'htmleditor')));
1240          $this->assertEquals('tinymce', $DB->get_field('user_preferences', 'value',
1241              array('userid' => $user2->id, 'name' => 'htmleditor')));
1242      }
1243  
1244      /**
1245       * Test set_user_preferences
1246       */
1247      public function test_set_user_preferences_save_invalid_pref() {
1248          global $DB;
1249          $this->resetAfterTest(true);
1250  
1251          $user1 = self::getDataGenerator()->create_user();
1252  
1253          // Save users preferences.
1254          $this->setAdminUser();
1255          $preferences = array(
1256              array(
1257                  'name' => 'some_random_pref',
1258                  'value' => 'abc',
1259                  'userid' => $user1->id,
1260              ),
1261          );
1262  
1263          $result = core_user_external::set_user_preferences($preferences);
1264          $result = \external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1265          $this->assertCount(1, $result['warnings']);
1266          $this->assertCount(0, $result['saved']);
1267          $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1268  
1269          // Nothing was written to DB.
1270          $this->assertEmpty($DB->count_records('user_preferences', array('name' => 'some_random_pref')));
1271      }
1272  
1273      /**
1274       * Test set_user_preferences for an invalid user
1275       */
1276      public function test_set_user_preferences_invalid_user() {
1277          $this->resetAfterTest(true);
1278  
1279          $this->setAdminUser();
1280          $preferences = array(
1281              array(
1282                  'name' => 'calendar_maxevents',
1283                  'value' => 4,
1284                  'userid' => -2
1285              )
1286          );
1287  
1288          $result = core_user_external::set_user_preferences($preferences);
1289          $result = \external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1290          $this->assertCount(1, $result['warnings']);
1291          $this->assertCount(0, $result['saved']);
1292          $this->assertEquals('invaliduser', $result['warnings'][0]['warningcode']);
1293          $this->assertEquals(-2, $result['warnings'][0]['itemid']);
1294      }
1295  
1296      /**
1297       * Test set_user_preferences using an invalid preference
1298       */
1299      public function test_set_user_preferences_invalid_preference() {
1300          global $USER, $DB;
1301  
1302          $this->resetAfterTest(true);
1303          // Create a very long value.
1304          $this->setAdminUser();
1305          $preferences = array(
1306              array(
1307                  'name' => 'calendar_maxevents',
1308                  'value' => str_repeat('a', 1334),
1309                  'userid' => $USER->id
1310              )
1311          );
1312  
1313          $result = core_user_external::set_user_preferences($preferences);
1314          $result = \external_api::clean_returnvalue(core_user_external::set_user_preferences_returns(), $result);
1315          $this->assertCount(0, $result['warnings']);
1316          $this->assertCount(1, $result['saved']);
1317          // Cleaned valud of the preference was saved.
1318          $this->assertEquals(1, $DB->get_field('user_preferences', 'value',
1319              array('userid' => $USER->id, 'name' => 'calendar_maxevents')));
1320      }
1321  
1322      /**
1323       * Test set_user_preferences for other user not being admin
1324       */
1325      public function test_set_user_preferences_capability() {
1326          $this->resetAfterTest(true);
1327  
1328          $user1 = self::getDataGenerator()->create_user();
1329          $user2 = self::getDataGenerator()->create_user();
1330  
1331          $this->setUser($user1);
1332          $preferences = array(
1333              array(
1334                  'name' => 'calendar_maxevents',
1335                  'value' => 4,
1336                  'userid' => $user2->id
1337              )
1338          );
1339  
1340          $result = core_user_external::set_user_preferences($preferences);
1341  
1342          $this->assertCount(1, $result['warnings']);
1343          $this->assertCount(0, $result['saved']);
1344          $this->assertEquals('nopermission', $result['warnings'][0]['warningcode']);
1345          $this->assertEquals($user2->id, $result['warnings'][0]['itemid']);
1346      }
1347  
1348      /**
1349       * Test update_user_preferences unsetting an existing preference.
1350       */
1351      public function test_update_user_preferences_unset() {
1352          global $DB;
1353          $this->resetAfterTest(true);
1354  
1355          $user = self::getDataGenerator()->create_user();
1356  
1357          // Save users preferences.
1358          $this->setAdminUser();
1359          $preferences = array(
1360              array(
1361                  'name' => 'htmleditor',
1362                  'value' => 'atto',
1363                  'userid' => $user->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(0, $result['warnings']);
1370          $this->assertCount(1, $result['saved']);
1371  
1372          // Get preference from DB to avoid cache.
1373          $this->assertEquals('atto', $DB->get_field('user_preferences', 'value',
1374              array('userid' => $user->id, 'name' => 'htmleditor')));
1375  
1376          // Now, unset.
1377          $result = core_user_external::update_user_preferences($user->id, null, array(array('type' => 'htmleditor')));
1378  
1379          $this->assertEquals(0, $DB->count_records('user_preferences', array('userid' => $user->id, 'name' => 'htmleditor')));
1380      }
1381  
1382      /**
1383       * Test agree_site_policy
1384       */
1385      public function test_agree_site_policy() {
1386          global $CFG, $DB, $USER;
1387          $this->resetAfterTest(true);
1388  
1389          $user = self::getDataGenerator()->create_user();
1390          $this->setUser($user);
1391  
1392          // Site policy not set.
1393          $result = core_user_external::agree_site_policy();
1394          $result = \external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1395          $this->assertFalse($result['status']);
1396          $this->assertCount(1, $result['warnings']);
1397          $this->assertEquals('nositepolicy', $result['warnings'][0]['warningcode']);
1398  
1399          // Set a policy issue.
1400          $CFG->sitepolicy = 'https://moodle.org';
1401          $this->assertEquals(0, $USER->policyagreed);
1402  
1403          $result = core_user_external::agree_site_policy();
1404          $result = \external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1405          $this->assertTrue($result['status']);
1406          $this->assertCount(0, $result['warnings']);
1407          $this->assertEquals(1, $USER->policyagreed);
1408          $this->assertEquals(1, $DB->get_field('user', 'policyagreed', array('id' => $USER->id)));
1409  
1410          // Try again, we should get a warning.
1411          $result = core_user_external::agree_site_policy();
1412          $result = \external_api::clean_returnvalue(core_user_external::agree_site_policy_returns(), $result);
1413          $this->assertFalse($result['status']);
1414          $this->assertCount(1, $result['warnings']);
1415          $this->assertEquals('alreadyagreed', $result['warnings'][0]['warningcode']);
1416  
1417          // Set something to make require_login throws an exception.
1418          $otheruser = self::getDataGenerator()->create_user();
1419          $this->setUser($otheruser);
1420  
1421          $DB->set_field('user', 'lastname', '', array('id' => $USER->id));
1422          $USER->lastname = '';
1423          try {
1424              $result = core_user_external::agree_site_policy();
1425              $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown');
1426          } catch (\moodle_exception $e) {
1427              $this->assertEquals('usernotfullysetup', $e->errorcode);
1428          } catch (\Exception $e) {
1429              $this->fail('Expecting \'usernotfullysetup\' moodle_exception to be thrown.');
1430          }
1431      }
1432  
1433      /**
1434       * Test get_private_files_info
1435       */
1436      public function test_get_private_files_info() {
1437  
1438          $this->resetAfterTest(true);
1439          $user = self::getDataGenerator()->create_user();
1440          $this->setUser($user);
1441          $usercontext = \context_user::instance($user->id);
1442  
1443          $filerecord = array(
1444              'contextid' => $usercontext->id,
1445              'component' => 'user',
1446              'filearea'  => 'private',
1447              'itemid'    => 0,
1448              'filepath'  => '/',
1449              'filename'  => 'thefile',
1450          );
1451  
1452          $fs = get_file_storage();
1453          $file = $fs->create_file_from_string($filerecord, 'abc');
1454  
1455          // Get my private files information.
1456          $result = core_user_external::get_private_files_info();
1457          $result = \external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1458          $this->assertEquals(1, $result['filecount']);
1459          $this->assertEquals($file->get_filesize(), $result['filesize']);
1460          $this->assertEquals(0, $result['foldercount']);
1461          $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1462  
1463          // As admin, get user information.
1464          $this->setAdminUser();
1465          $result = core_user_external::get_private_files_info($user->id);
1466          $result = \external_api::clean_returnvalue(core_user_external::get_private_files_info_returns(), $result);
1467          $this->assertEquals(1, $result['filecount']);
1468          $this->assertEquals($file->get_filesize(), $result['filesize']);
1469          $this->assertEquals(0, $result['foldercount']);
1470          $this->assertEquals($file->get_filesize(), $result['filesizewithoutreferences']);
1471      }
1472  
1473      /**
1474       * Test get_private_files_info missing permissions.
1475       */
1476      public function test_get_private_files_info_missing_permissions() {
1477  
1478          $this->resetAfterTest(true);
1479          $user1 = self::getDataGenerator()->create_user();
1480          $user2 = self::getDataGenerator()->create_user();
1481          $this->setUser($user1);
1482  
1483          $this->expectException('required_capability_exception');
1484          // Try to retrieve other user private files info.
1485          core_user_external::get_private_files_info($user2->id);
1486      }
1487  
1488      /**
1489       * Test the functionality of the {@see \core_user\external\search_identity} class.
1490       */
1491      public function test_external_search_identity() {
1492          global $CFG;
1493  
1494          $this->resetAfterTest(true);
1495          $this->setAdminUser();
1496  
1497          $user1 = self::getDataGenerator()->create_user([
1498              'firstname' => 'Firstone',
1499              'lastname' => 'Lastone',
1500              'username' => 'usernameone',
1501              'idnumber' => 'idnumberone',
1502              'email' => 'userone@example.com',
1503              'phone1' => 'tel1',
1504              'phone2' => 'tel2',
1505              'department' => 'Department Foo',
1506              'institution' => 'Institution Foo',
1507              'city' => 'City One',
1508              'country' => 'AU',
1509          ]);
1510  
1511          $user2 = self::getDataGenerator()->create_user([
1512              'firstname' => 'Firsttwo',
1513              'lastname' => 'Lasttwo',
1514              'username' => 'usernametwo',
1515              'idnumber' => 'idnumbertwo',
1516              'email' => 'usertwo@example.com',
1517              'phone1' => 'tel1',
1518              'phone2' => 'tel2',
1519              'department' => 'Department Foo',
1520              'institution' => 'Institution Foo',
1521              'city' => 'City One',
1522              'country' => 'AU',
1523          ]);
1524  
1525          $user3 = self::getDataGenerator()->create_user([
1526              'firstname' => 'Firstthree',
1527              'lastname' => 'Lastthree',
1528              'username' => 'usernamethree',
1529              'idnumber' => 'idnumberthree',
1530              'email' => 'userthree@example.com',
1531              'phone1' => 'tel1',
1532              'phone2' => 'tel2',
1533              'department' => 'Department Foo',
1534              'institution' => 'Institution Foo',
1535              'city' => 'City One',
1536              'country' => 'AU',
1537          ]);
1538  
1539          $CFG->showuseridentity = 'email,idnumber,city';
1540          $CFG->maxusersperpage = 3;
1541  
1542          $result = \core_user\external\search_identity::execute('Lastt');
1543          $result = \external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1544  
1545          $this->assertEquals(2, count($result['list']));
1546          $this->assertEquals(3, $result['maxusersperpage']);
1547          $this->assertEquals(false, $result['overflow']);
1548  
1549          foreach ($result['list'] as $user) {
1550              $this->assertEquals(3, count($user['extrafields']));
1551              $this->assertEquals('email', $user['extrafields'][0]['name']);
1552              $this->assertEquals('idnumber', $user['extrafields'][1]['name']);
1553              $this->assertEquals('city', $user['extrafields'][2]['name']);
1554          }
1555  
1556          $CFG->showuseridentity = 'username';
1557          $CFG->maxusersperpage = 2;
1558  
1559          $result = \core_user\external\search_identity::execute('Firstt');
1560          $result = \external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1561  
1562          $this->assertEquals(2, count($result['list']));
1563          $this->assertEquals(2, $result['maxusersperpage']);
1564          $this->assertEquals(false, $result['overflow']);
1565  
1566          foreach ($result['list'] as $user) {
1567              $this->assertEquals(1, count($user['extrafields']));
1568              $this->assertEquals('username', $user['extrafields'][0]['name']);
1569          }
1570  
1571          $CFG->showuseridentity = 'email';
1572          $CFG->maxusersperpage = 2;
1573  
1574          $result = \core_user\external\search_identity::execute('City One');
1575          $result = \external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1576  
1577          $this->assertEquals(0, count($result['list']));
1578          $this->assertEquals(2, $result['maxusersperpage']);
1579          $this->assertEquals(false, $result['overflow']);
1580  
1581          $CFG->showuseridentity = 'city';
1582          $CFG->maxusersperpage = 2;
1583  
1584          foreach ($result['list'] as $user) {
1585              $this->assertEquals(1, count($user['extrafields']));
1586              $this->assertEquals('username', $user['extrafields'][0]['name']);
1587          }
1588  
1589          $result = \core_user\external\search_identity::execute('City One');
1590          $result = \external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1591  
1592          $this->assertEquals(2, count($result['list']));
1593          $this->assertEquals(2, $result['maxusersperpage']);
1594          $this->assertEquals(true, $result['overflow']);
1595      }
1596  
1597      /**
1598       * Test functionality of the {@see \core_user\external\search_identity} class with alternativefullnameformat defined.
1599       */
1600      public function test_external_search_identity_with_alternativefullnameformat() {
1601          global $CFG;
1602  
1603          $this->resetAfterTest(true);
1604          $this->setAdminUser();
1605  
1606          $user1 = self::getDataGenerator()->create_user([
1607              'lastname' => '小柳',
1608              'lastnamephonetic' => 'Koyanagi',
1609              'firstname' => '秋',
1610              'firstnamephonetic' => 'Aki',
1611              'email' => 'koyanagiaki@example.com',
1612              'country' => 'JP',
1613          ]);
1614  
1615          $CFG->showuseridentity = 'email';
1616          $CFG->maxusersperpage = 3;
1617          $CFG->alternativefullnameformat =
1618              '<ruby>lastname firstname <rp>(</rp><rt>lastnamephonetic firstnamephonetic</rt><rp>)</rp></ruby>';
1619  
1620          $result = \core_user\external\search_identity::execute('Ak');
1621          $result = \external_api::clean_returnvalue(\core_user\external\search_identity::execute_returns(), $result);
1622  
1623          $this->assertEquals(1, count($result['list']));
1624          $this->assertEquals(3, $result['maxusersperpage']);
1625          $this->assertEquals(false, $result['overflow']);
1626  
1627          foreach ($result['list'] as $user) {
1628              $this->assertEquals(1, count($user['extrafields']));
1629              $this->assertEquals('email', $user['extrafields'][0]['name']);
1630          }
1631      }
1632  }