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