Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

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