See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 * External course functions unit tests 19 * 20 * @package core_course 21 * @category external 22 * @copyright 2012 Jerome Mouneyrac 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 30 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 31 32 /** 33 * External course functions unit tests 34 * 35 * @package core_course 36 * @category external 37 * @copyright 2012 Jerome Mouneyrac 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class externallib_test extends externallib_advanced_testcase { 41 //core_course_externallib_testcase 42 43 /** 44 * Tests set up 45 */ 46 protected function setUp(): void { 47 global $CFG; 48 require_once($CFG->dirroot . '/course/externallib.php'); 49 } 50 51 /** 52 * Test create_categories 53 */ 54 public function test_create_categories() { 55 56 global $DB; 57 58 $this->resetAfterTest(true); 59 60 // Set the required capabilities by the external function 61 $contextid = context_system::instance()->id; 62 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 63 64 // Create base categories. 65 $category1 = new stdClass(); 66 $category1->name = 'Root Test Category 1'; 67 $category2 = new stdClass(); 68 $category2->name = 'Root Test Category 2'; 69 $category2->idnumber = 'rootcattest2'; 70 $category2->desc = 'Description for root test category 1'; 71 $category2->theme = 'classic'; 72 $categories = array( 73 array('name' => $category1->name, 'parent' => 0), 74 array('name' => $category2->name, 'parent' => 0, 'idnumber' => $category2->idnumber, 75 'description' => $category2->desc, 'theme' => $category2->theme) 76 ); 77 78 $createdcats = core_course_external::create_categories($categories); 79 80 // We need to execute the return values cleaning process to simulate the web service server. 81 $createdcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdcats); 82 83 // Initially confirm that base data was inserted correctly. 84 $this->assertEquals($category1->name, $createdcats[0]['name']); 85 $this->assertEquals($category2->name, $createdcats[1]['name']); 86 87 // Save the ids. 88 $category1->id = $createdcats[0]['id']; 89 $category2->id = $createdcats[1]['id']; 90 91 // Create on sub category. 92 $category3 = new stdClass(); 93 $category3->name = 'Sub Root Test Category 3'; 94 $subcategories = array( 95 array('name' => $category3->name, 'parent' => $category1->id) 96 ); 97 98 $createdsubcats = core_course_external::create_categories($subcategories); 99 100 // We need to execute the return values cleaning process to simulate the web service server. 101 $createdsubcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdsubcats); 102 103 // Confirm that sub categories were inserted correctly. 104 $this->assertEquals($category3->name, $createdsubcats[0]['name']); 105 106 // Save the ids. 107 $category3->id = $createdsubcats[0]['id']; 108 109 // Calling the ws function should provide a new sortorder to give category1, 110 // category2, category3. New course categories are ordered by id not name. 111 $category1 = $DB->get_record('course_categories', array('id' => $category1->id)); 112 $category2 = $DB->get_record('course_categories', array('id' => $category2->id)); 113 $category3 = $DB->get_record('course_categories', array('id' => $category3->id)); 114 115 // sortorder sequence (and sortorder) must be: 116 // category 1 117 // category 3 118 // category 2 119 $this->assertGreaterThan($category1->sortorder, $category3->sortorder); 120 $this->assertGreaterThan($category3->sortorder, $category2->sortorder); 121 122 // Call without required capability 123 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 124 $this->expectException('required_capability_exception'); 125 $createdsubcats = core_course_external::create_categories($subcategories); 126 127 } 128 129 /** 130 * Test delete categories 131 */ 132 public function test_delete_categories() { 133 global $DB; 134 135 $this->resetAfterTest(true); 136 137 // Set the required capabilities by the external function 138 $contextid = context_system::instance()->id; 139 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 140 141 $category1 = self::getDataGenerator()->create_category(); 142 $category2 = self::getDataGenerator()->create_category( 143 array('parent' => $category1->id)); 144 $category3 = self::getDataGenerator()->create_category(); 145 $category4 = self::getDataGenerator()->create_category( 146 array('parent' => $category3->id)); 147 $category5 = self::getDataGenerator()->create_category( 148 array('parent' => $category4->id)); 149 150 //delete category 1 and 2 + delete category 4, category 5 moved under category 3 151 core_course_external::delete_categories(array( 152 array('id' => $category1->id, 'recursive' => 1), 153 array('id' => $category4->id) 154 )); 155 156 //check $category 1 and 2 are deleted 157 $notdeletedcount = $DB->count_records_select('course_categories', 158 'id IN ( ' . $category1->id . ',' . $category2->id . ',' . $category4->id . ')'); 159 $this->assertEquals(0, $notdeletedcount); 160 161 //check that $category5 as $category3 for parent 162 $dbcategory5 = $DB->get_record('course_categories', array('id' => $category5->id)); 163 $this->assertEquals($dbcategory5->path, $category3->path . '/' . $category5->id); 164 165 // Call without required capability 166 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 167 $this->expectException('required_capability_exception'); 168 $createdsubcats = core_course_external::delete_categories( 169 array(array('id' => $category3->id))); 170 } 171 172 /** 173 * Test get categories 174 */ 175 public function test_get_categories() { 176 global $DB; 177 178 $this->resetAfterTest(true); 179 180 $generatedcats = array(); 181 $category1data['idnumber'] = 'idnumbercat1'; 182 $category1data['name'] = 'Category 1 for PHPunit test'; 183 $category1data['description'] = 'Category 1 description'; 184 $category1data['descriptionformat'] = FORMAT_MOODLE; 185 $category1 = self::getDataGenerator()->create_category($category1data); 186 $generatedcats[$category1->id] = $category1; 187 $category2 = self::getDataGenerator()->create_category( 188 array('parent' => $category1->id)); 189 $generatedcats[$category2->id] = $category2; 190 $category6 = self::getDataGenerator()->create_category( 191 array('parent' => $category1->id, 'visible' => 0)); 192 $generatedcats[$category6->id] = $category6; 193 $category3 = self::getDataGenerator()->create_category(); 194 $generatedcats[$category3->id] = $category3; 195 $category4 = self::getDataGenerator()->create_category( 196 array('parent' => $category3->id)); 197 $generatedcats[$category4->id] = $category4; 198 $category5 = self::getDataGenerator()->create_category( 199 array('parent' => $category4->id)); 200 $generatedcats[$category5->id] = $category5; 201 202 // Set the required capabilities by the external function. 203 $context = context_system::instance(); 204 $roleid = $this->assignUserCapability('moodle/category:manage', $context->id); 205 $this->assignUserCapability('moodle/category:viewhiddencategories', $context->id, $roleid); 206 207 // Retrieve category1 + sub-categories except not visible ones 208 $categories = core_course_external::get_categories(array( 209 array('key' => 'id', 'value' => $category1->id), 210 array('key' => 'visible', 'value' => 1)), 1); 211 212 // We need to execute the return values cleaning process to simulate the web service server. 213 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 214 215 // Check we retrieve the good total number of categories. 216 $this->assertEquals(2, count($categories)); 217 218 // Check the return values 219 foreach ($categories as $category) { 220 $generatedcat = $generatedcats[$category['id']]; 221 $this->assertEquals($category['idnumber'], $generatedcat->idnumber); 222 $this->assertEquals($category['name'], $generatedcat->name); 223 // Description was converted to the HTML format. 224 $this->assertEquals($category['description'], format_text($generatedcat->description, FORMAT_MOODLE, array('para' => false))); 225 $this->assertEquals($category['descriptionformat'], FORMAT_HTML); 226 } 227 228 // Check categories by ids. 229 $ids = implode(',', array_keys($generatedcats)); 230 $categories = core_course_external::get_categories(array( 231 array('key' => 'ids', 'value' => $ids)), 0); 232 233 // We need to execute the return values cleaning process to simulate the web service server. 234 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 235 236 // Check we retrieve the good total number of categories. 237 $this->assertEquals(6, count($categories)); 238 // Check ids. 239 $returnedids = []; 240 foreach ($categories as $category) { 241 $returnedids[] = $category['id']; 242 } 243 // Sort the arrays upon comparision. 244 $this->assertEqualsCanonicalizing(array_keys($generatedcats), $returnedids); 245 246 // Check different params. 247 $categories = core_course_external::get_categories(array( 248 array('key' => 'id', 'value' => $category1->id), 249 array('key' => 'ids', 'value' => $category1->id), 250 array('key' => 'idnumber', 'value' => $category1->idnumber), 251 array('key' => 'visible', 'value' => 1)), 0); 252 253 // We need to execute the return values cleaning process to simulate the web service server. 254 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 255 256 $this->assertEquals(1, count($categories)); 257 258 // Same query, but forcing a parameters clean. 259 $categories = core_course_external::get_categories(array( 260 array('key' => 'id', 'value' => "$category1->id"), 261 array('key' => 'idnumber', 'value' => $category1->idnumber), 262 array('key' => 'name', 'value' => $category1->name . "<br/>"), 263 array('key' => 'visible', 'value' => '1')), 0); 264 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 265 266 $this->assertEquals(1, count($categories)); 267 268 // Retrieve categories from parent. 269 $categories = core_course_external::get_categories(array( 270 array('key' => 'parent', 'value' => $category3->id)), 1); 271 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 272 273 $this->assertEquals(2, count($categories)); 274 275 // Retrieve all categories. 276 $categories = core_course_external::get_categories(); 277 278 // We need to execute the return values cleaning process to simulate the web service server. 279 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 280 281 $this->assertEquals($DB->count_records('course_categories'), count($categories)); 282 283 $this->unassignUserCapability('moodle/category:viewhiddencategories', $context->id, $roleid); 284 285 // Ensure maxdepthcategory is 2 and retrieve all categories without category:viewhiddencategories capability. 286 // It should retrieve all visible categories as well. 287 set_config('maxcategorydepth', 2); 288 $categories = core_course_external::get_categories(); 289 290 // We need to execute the return values cleaning process to simulate the web service server. 291 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 292 293 $this->assertEquals($DB->count_records('course_categories', array('visible' => 1)), count($categories)); 294 295 // Call without required capability (it will fail cause of the search on idnumber). 296 $this->expectException('moodle_exception'); 297 $categories = core_course_external::get_categories(array( 298 array('key' => 'id', 'value' => $category1->id), 299 array('key' => 'idnumber', 'value' => $category1->idnumber), 300 array('key' => 'visible', 'value' => 1)), 0); 301 } 302 303 /** 304 * Test update_categories 305 */ 306 public function test_update_categories() { 307 global $DB; 308 309 $this->resetAfterTest(true); 310 311 // Set the required capabilities by the external function 312 $contextid = context_system::instance()->id; 313 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 314 315 // Create base categories. 316 $category1data['idnumber'] = 'idnumbercat1'; 317 $category1data['name'] = 'Category 1 for PHPunit test'; 318 $category1data['description'] = 'Category 1 description'; 319 $category1data['descriptionformat'] = FORMAT_MOODLE; 320 $category1 = self::getDataGenerator()->create_category($category1data); 321 $category2 = self::getDataGenerator()->create_category( 322 array('parent' => $category1->id)); 323 $category3 = self::getDataGenerator()->create_category(); 324 $category4 = self::getDataGenerator()->create_category( 325 array('parent' => $category3->id)); 326 $category5 = self::getDataGenerator()->create_category( 327 array('parent' => $category4->id)); 328 329 // We update all category1 attribut. 330 // Then we move cat4 and cat5 parent: cat3 => cat1 331 $categories = array( 332 array('id' => $category1->id, 333 'name' => $category1->name . '_updated', 334 'idnumber' => $category1->idnumber . '_updated', 335 'description' => $category1->description . '_updated', 336 'descriptionformat' => FORMAT_HTML, 337 'theme' => $category1->theme), 338 array('id' => $category4->id, 'parent' => $category1->id)); 339 340 core_course_external::update_categories($categories); 341 342 // Check the values were updated. 343 $dbcategories = $DB->get_records_select('course_categories', 344 'id IN (' . $category1->id . ',' . $category2->id . ',' . $category2->id 345 . ',' . $category3->id . ',' . $category4->id . ',' . $category5->id .')'); 346 $this->assertEquals($category1->name . '_updated', 347 $dbcategories[$category1->id]->name); 348 $this->assertEquals($category1->idnumber . '_updated', 349 $dbcategories[$category1->id]->idnumber); 350 $this->assertEquals($category1->description . '_updated', 351 $dbcategories[$category1->id]->description); 352 $this->assertEquals(FORMAT_HTML, $dbcategories[$category1->id]->descriptionformat); 353 354 // Check that category4 and category5 have been properly moved. 355 $this->assertEquals('/' . $category1->id . '/' . $category4->id, 356 $dbcategories[$category4->id]->path); 357 $this->assertEquals('/' . $category1->id . '/' . $category4->id . '/' . $category5->id, 358 $dbcategories[$category5->id]->path); 359 360 // Call without required capability. 361 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 362 $this->expectException('required_capability_exception'); 363 core_course_external::update_categories($categories); 364 } 365 366 /** 367 * Test update_categories method for moving categories 368 */ 369 public function test_update_categories_moving() { 370 $this->resetAfterTest(); 371 372 // Create data. 373 $categorya = self::getDataGenerator()->create_category([ 374 'name' => 'CAT_A', 375 ]); 376 $categoryasub = self::getDataGenerator()->create_category([ 377 'name' => 'SUBCAT_A', 378 'parent' => $categorya->id 379 ]); 380 $categoryb = self::getDataGenerator()->create_category([ 381 'name' => 'CAT_B', 382 ]); 383 384 // Create a new test user. 385 $testuser = self::getDataGenerator()->create_user(); 386 $this->setUser($testuser); 387 388 // Set the capability for CAT_A only. 389 $contextcata = context_coursecat::instance($categorya->id); 390 $roleid = $this->assignUserCapability('moodle/category:manage', $contextcata->id); 391 392 // Then we move SUBCAT_A parent: CAT_A => CAT_B. 393 $categories = [ 394 [ 395 'id' => $categoryasub->id, 396 'parent' => $categoryb->id 397 ] 398 ]; 399 400 $this->expectException('required_capability_exception'); 401 core_course_external::update_categories($categories); 402 } 403 404 /** 405 * Test create_courses numsections 406 */ 407 public function test_create_course_numsections() { 408 global $DB; 409 410 $this->resetAfterTest(true); 411 412 // Set the required capabilities by the external function. 413 $contextid = context_system::instance()->id; 414 $roleid = $this->assignUserCapability('moodle/course:create', $contextid); 415 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 416 417 $numsections = 10; 418 $category = self::getDataGenerator()->create_category(); 419 420 // Create base categories. 421 $course1['fullname'] = 'Test course 1'; 422 $course1['shortname'] = 'Testcourse1'; 423 $course1['categoryid'] = $category->id; 424 $course1['courseformatoptions'][] = array('name' => 'numsections', 'value' => $numsections); 425 426 $courses = array($course1); 427 428 $createdcourses = core_course_external::create_courses($courses); 429 foreach ($createdcourses as $createdcourse) { 430 $existingsections = $DB->get_records('course_sections', array('course' => $createdcourse['id'])); 431 $modinfo = get_fast_modinfo($createdcourse['id']); 432 $sections = $modinfo->get_section_info_all(); 433 $this->assertEquals(count($sections), $numsections + 1); // Includes generic section. 434 $this->assertEquals(count($existingsections), $numsections + 1); // Includes generic section. 435 } 436 } 437 438 /** 439 * Test create_courses 440 */ 441 public function test_create_courses() { 442 global $DB; 443 444 $this->resetAfterTest(true); 445 446 // Enable course completion. 447 set_config('enablecompletion', 1); 448 // Enable course themes. 449 set_config('allowcoursethemes', 1); 450 451 // Custom fields. 452 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 453 454 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 455 'categoryid' => $fieldcategory->get('id'), 456 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL]]; 457 $field = self::getDataGenerator()->create_custom_field($customfield); 458 459 // Set the required capabilities by the external function 460 $contextid = context_system::instance()->id; 461 $roleid = $this->assignUserCapability('moodle/course:create', $contextid); 462 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 463 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid); 464 465 $category = self::getDataGenerator()->create_category(); 466 467 // Create base categories. 468 $course1['fullname'] = 'Test course 1'; 469 $course1['shortname'] = 'Testcourse1'; 470 $course1['categoryid'] = $category->id; 471 $course2['fullname'] = 'Test course 2'; 472 $course2['shortname'] = 'Testcourse2'; 473 $course2['categoryid'] = $category->id; 474 $course2['idnumber'] = 'testcourse2idnumber'; 475 $course2['summary'] = 'Description for course 2'; 476 $course2['summaryformat'] = FORMAT_MOODLE; 477 $course2['format'] = 'weeks'; 478 $course2['showgrades'] = 1; 479 $course2['newsitems'] = 3; 480 $course2['startdate'] = 1420092000; // 01/01/2015. 481 $course2['enddate'] = 1422669600; // 01/31/2015. 482 $course2['numsections'] = 4; 483 $course2['maxbytes'] = 100000; 484 $course2['showreports'] = 1; 485 $course2['visible'] = 0; 486 $course2['hiddensections'] = 0; 487 $course2['groupmode'] = 0; 488 $course2['groupmodeforce'] = 0; 489 $course2['defaultgroupingid'] = 0; 490 $course2['enablecompletion'] = 1; 491 $course2['completionnotify'] = 1; 492 $course2['lang'] = 'en'; 493 $course2['forcetheme'] = 'classic'; 494 $course2['courseformatoptions'][] = array('name' => 'automaticenddate', 'value' => 0); 495 $course3['fullname'] = 'Test course 3'; 496 $course3['shortname'] = 'Testcourse3'; 497 $course3['categoryid'] = $category->id; 498 $course3['format'] = 'topics'; 499 $course3options = array('numsections' => 8, 500 'hiddensections' => 1, 501 'coursedisplay' => 1); 502 $course3['courseformatoptions'] = array(); 503 foreach ($course3options as $key => $value) { 504 $course3['courseformatoptions'][] = array('name' => $key, 'value' => $value); 505 } 506 $course4['fullname'] = 'Test course with custom fields'; 507 $course4['shortname'] = 'Testcoursecustomfields'; 508 $course4['categoryid'] = $category->id; 509 $course4['customfields'] = [['shortname' => $customfield['shortname'], 'value' => 'Test value']]; 510 $courses = array($course4, $course1, $course2, $course3); 511 512 $createdcourses = core_course_external::create_courses($courses); 513 514 // We need to execute the return values cleaning process to simulate the web service server. 515 $createdcourses = external_api::clean_returnvalue(core_course_external::create_courses_returns(), $createdcourses); 516 517 // Check that right number of courses were created. 518 $this->assertEquals(4, count($createdcourses)); 519 520 // Check that the courses were correctly created. 521 foreach ($createdcourses as $createdcourse) { 522 $courseinfo = course_get_format($createdcourse['id'])->get_course(); 523 524 if ($createdcourse['shortname'] == $course2['shortname']) { 525 $this->assertEquals($courseinfo->fullname, $course2['fullname']); 526 $this->assertEquals($courseinfo->shortname, $course2['shortname']); 527 $this->assertEquals($courseinfo->category, $course2['categoryid']); 528 $this->assertEquals($courseinfo->idnumber, $course2['idnumber']); 529 $this->assertEquals($courseinfo->summary, $course2['summary']); 530 $this->assertEquals($courseinfo->summaryformat, $course2['summaryformat']); 531 $this->assertEquals($courseinfo->format, $course2['format']); 532 $this->assertEquals($courseinfo->showgrades, $course2['showgrades']); 533 $this->assertEquals($courseinfo->newsitems, $course2['newsitems']); 534 $this->assertEquals($courseinfo->startdate, $course2['startdate']); 535 $this->assertEquals($courseinfo->enddate, $course2['enddate']); 536 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(), $course2['numsections']); 537 $this->assertEquals($courseinfo->maxbytes, $course2['maxbytes']); 538 $this->assertEquals($courseinfo->showreports, $course2['showreports']); 539 $this->assertEquals($courseinfo->visible, $course2['visible']); 540 $this->assertEquals($courseinfo->hiddensections, $course2['hiddensections']); 541 $this->assertEquals($courseinfo->groupmode, $course2['groupmode']); 542 $this->assertEquals($courseinfo->groupmodeforce, $course2['groupmodeforce']); 543 $this->assertEquals($courseinfo->defaultgroupingid, $course2['defaultgroupingid']); 544 $this->assertEquals($courseinfo->completionnotify, $course2['completionnotify']); 545 $this->assertEquals($courseinfo->lang, $course2['lang']); 546 $this->assertEquals($courseinfo->theme, $course2['forcetheme']); 547 548 // We enabled completion at the beginning of the test. 549 $this->assertEquals($courseinfo->enablecompletion, $course2['enablecompletion']); 550 551 } else if ($createdcourse['shortname'] == $course1['shortname']) { 552 $courseconfig = get_config('moodlecourse'); 553 $this->assertEquals($courseinfo->fullname, $course1['fullname']); 554 $this->assertEquals($courseinfo->shortname, $course1['shortname']); 555 $this->assertEquals($courseinfo->category, $course1['categoryid']); 556 $this->assertEquals($courseinfo->summaryformat, FORMAT_HTML); 557 $this->assertEquals($courseinfo->format, $courseconfig->format); 558 $this->assertEquals($courseinfo->showgrades, $courseconfig->showgrades); 559 $this->assertEquals($courseinfo->newsitems, $courseconfig->newsitems); 560 $this->assertEquals($courseinfo->maxbytes, $courseconfig->maxbytes); 561 $this->assertEquals($courseinfo->showreports, $courseconfig->showreports); 562 $this->assertEquals($courseinfo->groupmode, $courseconfig->groupmode); 563 $this->assertEquals($courseinfo->groupmodeforce, $courseconfig->groupmodeforce); 564 $this->assertEquals($courseinfo->defaultgroupingid, 0); 565 } else if ($createdcourse['shortname'] == $course3['shortname']) { 566 $this->assertEquals($courseinfo->fullname, $course3['fullname']); 567 $this->assertEquals($courseinfo->shortname, $course3['shortname']); 568 $this->assertEquals($courseinfo->category, $course3['categoryid']); 569 $this->assertEquals($courseinfo->format, $course3['format']); 570 $this->assertEquals($courseinfo->hiddensections, $course3options['hiddensections']); 571 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(), 572 $course3options['numsections']); 573 $this->assertEquals($courseinfo->coursedisplay, $course3options['coursedisplay']); 574 } else if ($createdcourse['shortname'] == $course4['shortname']) { 575 $this->assertEquals($courseinfo->fullname, $course4['fullname']); 576 $this->assertEquals($courseinfo->shortname, $course4['shortname']); 577 $this->assertEquals($courseinfo->category, $course4['categoryid']); 578 579 $handler = core_course\customfield\course_handler::create(); 580 $customfields = $handler->export_instance_data_object($createdcourse['id']); 581 $this->assertEquals((object)['test' => 'Test value'], $customfields); 582 } else { 583 throw new moodle_exception('Unexpected shortname'); 584 } 585 } 586 587 // Call without required capability 588 $this->unassignUserCapability('moodle/course:create', $contextid, $roleid); 589 $this->expectException('required_capability_exception'); 590 $createdsubcats = core_course_external::create_courses($courses); 591 } 592 593 /** 594 * Data provider for testing empty fields produce expected exceptions 595 * 596 * @see test_create_courses_empty_field 597 * @see test_update_courses_empty_field 598 * 599 * @return array 600 */ 601 public function course_empty_field_provider(): array { 602 return [ 603 [[ 604 'fullname' => '', 605 'shortname' => 'ws101', 606 ], 'fullname'], 607 [[ 608 'fullname' => ' ', 609 'shortname' => 'ws101', 610 ], 'fullname'], 611 [[ 612 'fullname' => 'Web Services', 613 'shortname' => '', 614 ], 'shortname'], 615 [[ 616 'fullname' => 'Web Services', 617 'shortname' => ' ', 618 ], 'shortname'], 619 ]; 620 } 621 622 /** 623 * Test creating courses with empty fields throws an exception 624 * 625 * @param array $course 626 * @param string $expectedemptyfield 627 * 628 * @dataProvider course_empty_field_provider 629 */ 630 public function test_create_courses_empty_field(array $course, string $expectedemptyfield): void { 631 $this->resetAfterTest(); 632 $this->setAdminUser(); 633 634 // Create a category for the new course. 635 $course['categoryid'] = $this->getDataGenerator()->create_category()->id; 636 637 $this->expectException(moodle_exception::class); 638 $this->expectExceptionMessageMatches("/{$expectedemptyfield}/"); 639 core_course_external::create_courses([$course]); 640 } 641 642 /** 643 * Test updating courses with empty fields returns warnings 644 * 645 * @param array $course 646 * @param string $expectedemptyfield 647 * 648 * @dataProvider course_empty_field_provider 649 */ 650 public function test_update_courses_empty_field(array $course, string $expectedemptyfield): void { 651 $this->resetAfterTest(); 652 $this->setAdminUser(); 653 654 // Create a course to update. 655 $course['id'] = $this->getDataGenerator()->create_course()->id; 656 657 $result = core_course_external::update_courses([$course]); 658 $result = core_course_external::clean_returnvalue(core_course_external::update_courses_returns(), $result); 659 660 $this->assertCount(1, $result['warnings']); 661 662 $warning = reset($result['warnings']); 663 $this->assertEquals('errorinvalidparam', $warning['warningcode']); 664 $this->assertStringContainsString($expectedemptyfield, $warning['message']); 665 } 666 667 /** 668 * Test delete_courses 669 */ 670 public function test_delete_courses() { 671 global $DB, $USER; 672 673 $this->resetAfterTest(true); 674 675 // Admin can delete a course. 676 $this->setAdminUser(); 677 // Validate_context() will fail as the email is not set by $this->setAdminUser(). 678 $USER->email = 'emailtopass@example.com'; 679 680 $course1 = self::getDataGenerator()->create_course(); 681 $course2 = self::getDataGenerator()->create_course(); 682 $course3 = self::getDataGenerator()->create_course(); 683 684 // Delete courses. 685 $result = core_course_external::delete_courses(array($course1->id, $course2->id)); 686 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 687 // Check for 0 warnings. 688 $this->assertEquals(0, count($result['warnings'])); 689 690 // Check $course 1 and 2 are deleted. 691 $notdeletedcount = $DB->count_records_select('course', 692 'id IN ( ' . $course1->id . ',' . $course2->id . ')'); 693 $this->assertEquals(0, $notdeletedcount); 694 695 // Try to delete non-existent course. 696 $result = core_course_external::delete_courses(array($course1->id)); 697 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 698 // Check for 1 warnings. 699 $this->assertEquals(1, count($result['warnings'])); 700 701 // Try to delete Frontpage course. 702 $result = core_course_external::delete_courses(array(0)); 703 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 704 // Check for 1 warnings. 705 $this->assertEquals(1, count($result['warnings'])); 706 707 // Fail when the user has access to course (enrolled) but does not have permission or is not admin. 708 $student1 = self::getDataGenerator()->create_user(); 709 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 710 $this->getDataGenerator()->enrol_user($student1->id, 711 $course3->id, 712 $studentrole->id); 713 $this->setUser($student1); 714 $result = core_course_external::delete_courses(array($course3->id)); 715 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 716 // Check for 1 warnings. 717 $this->assertEquals(1, count($result['warnings'])); 718 719 // Fail when the user is not allow to access the course (enrolled) or is not admin. 720 $this->setGuestUser(); 721 $this->expectException('require_login_exception'); 722 723 $result = core_course_external::delete_courses(array($course3->id)); 724 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 725 } 726 727 /** 728 * Test get_courses 729 */ 730 public function test_get_courses () { 731 global $DB; 732 733 $this->resetAfterTest(true); 734 735 $generatedcourses = array(); 736 $coursedata['idnumber'] = 'idnumbercourse1'; 737 // Adding tags here to check that format_string is applied. 738 $coursedata['fullname'] = '<b>Course 1 for PHPunit test</b>'; 739 $coursedata['shortname'] = '<b>Course 1 for PHPunit test</b>'; 740 $coursedata['summary'] = 'Course 1 description'; 741 $coursedata['summaryformat'] = FORMAT_MOODLE; 742 $course1 = self::getDataGenerator()->create_course($coursedata); 743 744 $fieldcategory = self::getDataGenerator()->create_custom_field_category( 745 ['name' => 'Other fields']); 746 747 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 748 'categoryid' => $fieldcategory->get('id')]; 749 $field = self::getDataGenerator()->create_custom_field($customfield); 750 751 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; 752 753 $generatedcourses[$course1->id] = $course1; 754 $course2 = self::getDataGenerator()->create_course(); 755 $generatedcourses[$course2->id] = $course2; 756 $course3 = self::getDataGenerator()->create_course(array('format' => 'topics')); 757 $generatedcourses[$course3->id] = $course3; 758 $course4 = self::getDataGenerator()->create_course(['customfields' => [$customfieldvalue]]); 759 $generatedcourses[$course4->id] = $course4; 760 761 // Set the required capabilities by the external function. 762 $context = context_system::instance(); 763 $roleid = $this->assignUserCapability('moodle/course:view', $context->id); 764 $this->assignUserCapability('moodle/course:update', 765 context_course::instance($course1->id)->id, $roleid); 766 $this->assignUserCapability('moodle/course:update', 767 context_course::instance($course2->id)->id, $roleid); 768 $this->assignUserCapability('moodle/course:update', 769 context_course::instance($course3->id)->id, $roleid); 770 $this->assignUserCapability('moodle/course:update', 771 context_course::instance($course4->id)->id, $roleid); 772 773 $courses = core_course_external::get_courses(array('ids' => 774 array($course1->id, $course2->id, $course4->id))); 775 776 // We need to execute the return values cleaning process to simulate the web service server. 777 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 778 779 // Check we retrieve the good total number of courses. 780 $this->assertEquals(3, count($courses)); 781 782 foreach ($courses as $course) { 783 $coursecontext = context_course::instance($course['id']); 784 $dbcourse = $generatedcourses[$course['id']]; 785 $this->assertEquals($course['idnumber'], $dbcourse->idnumber); 786 $this->assertEquals($course['fullname'], external_format_string($dbcourse->fullname, $coursecontext->id)); 787 $this->assertEquals($course['displayname'], external_format_string(get_course_display_name_for_list($dbcourse), 788 $coursecontext->id)); 789 // Summary was converted to the HTML format. 790 $this->assertEquals($course['summary'], format_text($dbcourse->summary, FORMAT_MOODLE, array('para' => false))); 791 $this->assertEquals($course['summaryformat'], FORMAT_HTML); 792 $this->assertEquals($course['shortname'], external_format_string($dbcourse->shortname, $coursecontext->id)); 793 $this->assertEquals($course['categoryid'], $dbcourse->category); 794 $this->assertEquals($course['format'], $dbcourse->format); 795 $this->assertEquals($course['showgrades'], $dbcourse->showgrades); 796 $this->assertEquals($course['newsitems'], $dbcourse->newsitems); 797 $this->assertEquals($course['startdate'], $dbcourse->startdate); 798 $this->assertEquals($course['enddate'], $dbcourse->enddate); 799 $this->assertEquals($course['numsections'], course_get_format($dbcourse)->get_last_section_number()); 800 $this->assertEquals($course['maxbytes'], $dbcourse->maxbytes); 801 $this->assertEquals($course['showreports'], $dbcourse->showreports); 802 $this->assertEquals($course['visible'], $dbcourse->visible); 803 $this->assertEquals($course['hiddensections'], $dbcourse->hiddensections); 804 $this->assertEquals($course['groupmode'], $dbcourse->groupmode); 805 $this->assertEquals($course['groupmodeforce'], $dbcourse->groupmodeforce); 806 $this->assertEquals($course['defaultgroupingid'], $dbcourse->defaultgroupingid); 807 $this->assertEquals($course['completionnotify'], $dbcourse->completionnotify); 808 $this->assertEquals($course['lang'], $dbcourse->lang); 809 $this->assertEquals($course['forcetheme'], $dbcourse->theme); 810 $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion); 811 if ($dbcourse->format === 'topics') { 812 $this->assertEquals($course['courseformatoptions'], array( 813 array('name' => 'hiddensections', 'value' => $dbcourse->hiddensections), 814 array('name' => 'coursedisplay', 'value' => $dbcourse->coursedisplay), 815 )); 816 } 817 818 // Assert custom field that we previously added to test course 4. 819 if ($dbcourse->id == $course4->id) { 820 $this->assertEquals([ 821 'shortname' => $customfield['shortname'], 822 'name' => $customfield['name'], 823 'type' => $customfield['type'], 824 'value' => $customfieldvalue['value'], 825 'valueraw' => $customfieldvalue['value'], 826 ], $course['customfields'][0]); 827 } 828 } 829 830 // Get all courses in the DB 831 $courses = core_course_external::get_courses(array()); 832 833 // We need to execute the return values cleaning process to simulate the web service server. 834 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 835 836 $this->assertEquals($DB->count_records('course'), count($courses)); 837 } 838 839 /** 840 * Test retrieving courses returns custom field data 841 */ 842 public function test_get_courses_customfields(): void { 843 $this->resetAfterTest(); 844 $this->setAdminUser(); 845 846 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 847 $datefield = $this->getDataGenerator()->create_custom_field([ 848 'categoryid' => $fieldcategory->get('id'), 849 'shortname' => 'mydate', 850 'name' => 'My date', 851 'type' => 'date', 852 ]); 853 854 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 855 [ 856 'shortname' => $datefield->get('shortname'), 857 'value' => 1580389200, // 30/01/2020 13:00 GMT. 858 ], 859 ]]); 860 861 $courses = external_api::clean_returnvalue( 862 core_course_external::get_courses_returns(), 863 core_course_external::get_courses(['ids' => [$newcourse->id]]) 864 ); 865 866 $this->assertCount(1, $courses); 867 $course = reset($courses); 868 869 $this->assertArrayHasKey('customfields', $course); 870 $this->assertCount(1, $course['customfields']); 871 872 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 873 $this->assertEquals([ 874 'name' => $datefield->get('name'), 875 'shortname' => $datefield->get('shortname'), 876 'type' => $datefield->get('type'), 877 'value' => userdate(1580389200), 878 'valueraw' => 1580389200, 879 ], reset($course['customfields'])); 880 } 881 882 /** 883 * Test get_courses without capability 884 */ 885 public function test_get_courses_without_capability() { 886 $this->resetAfterTest(true); 887 888 $course1 = $this->getDataGenerator()->create_course(); 889 $this->setUser($this->getDataGenerator()->create_user()); 890 891 // No permissions are required to get the site course. 892 $courses = core_course_external::get_courses(array('ids' => [SITEID])); 893 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 894 895 $this->assertEquals(1, count($courses)); 896 $this->assertEquals('PHPUnit test site', $courses[0]['fullname']); 897 $this->assertEquals('site', $courses[0]['format']); 898 899 // Requesting course without being enrolled or capability to view it will throw an exception. 900 try { 901 core_course_external::get_courses(array('ids' => [$course1->id])); 902 $this->fail('Exception expected'); 903 } catch (moodle_exception $e) { 904 $this->assertEquals(1, preg_match('/Course or activity not accessible. \(Not enrolled\)/', $e->getMessage())); 905 } 906 } 907 908 /** 909 * Test search_courses 910 */ 911 public function test_search_courses () { 912 913 global $DB; 914 915 $this->resetAfterTest(true); 916 $this->setAdminUser(); 917 $generatedcourses = array(); 918 $coursedata1['fullname'] = 'FIRST COURSE'; 919 $course1 = self::getDataGenerator()->create_course($coursedata1); 920 921 $page = new moodle_page(); 922 $page->set_course($course1); 923 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 924 925 $coursedata2['fullname'] = 'SECOND COURSE'; 926 $course2 = self::getDataGenerator()->create_course($coursedata2); 927 928 $page = new moodle_page(); 929 $page->set_course($course2); 930 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 931 932 // Search by name. 933 $results = core_course_external::search_courses('search', 'FIRST'); 934 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 935 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 936 $this->assertCount(1, $results['courses']); 937 938 // Create the forum. 939 $record = new stdClass(); 940 $record->introformat = FORMAT_HTML; 941 $record->course = $course2->id; 942 // Set Aggregate type = Average of ratings. 943 $forum = self::getDataGenerator()->create_module('forum', $record); 944 945 // Search by module. 946 $results = core_course_external::search_courses('modulelist', 'forum'); 947 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 948 $this->assertEquals(1, $results['total']); 949 950 // Enable coursetag option. 951 set_config('block_tags_showcoursetags', true); 952 // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2. 953 core_tag_tag::set_item_tags('core', 'course', $course2->id, context_course::instance($course2->id), 954 array('TAG-LABEL ON SECOND COURSE')); 955 $taginstance = $DB->get_record('tag_instance', 956 array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST); 957 958 // Search by tagid. 959 $results = core_course_external::search_courses('tagid', $taginstance->tagid); 960 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 961 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 962 963 // Search by block (use news_items default block). 964 $blockid = $DB->get_field('block', 'id', array('name' => 'news_items')); 965 $results = core_course_external::search_courses('blocklist', $blockid); 966 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 967 $this->assertEquals(2, $results['total']); 968 969 // Now as a normal user. 970 $user = self::getDataGenerator()->create_user(); 971 972 // Add a 3rd, hidden, course we shouldn't see, even when enrolled as student. 973 $coursedata3['fullname'] = 'HIDDEN COURSE'; 974 $coursedata3['visible'] = 0; 975 $course3 = self::getDataGenerator()->create_course($coursedata3); 976 $this->getDataGenerator()->enrol_user($user->id, $course3->id, 'student'); 977 978 $this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student'); 979 $this->setUser($user); 980 981 $results = core_course_external::search_courses('search', 'FIRST'); 982 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 983 $this->assertCount(1, $results['courses']); 984 $this->assertEquals(1, $results['total']); 985 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 986 987 // Check that we can see all courses without the limit to enrolled setting. 988 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 0); 989 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 990 $this->assertCount(2, $results['courses']); 991 $this->assertEquals(2, $results['total']); 992 993 // Check that we only see our enrolled course when limiting. 994 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 1); 995 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 996 $this->assertCount(1, $results['courses']); 997 $this->assertEquals(1, $results['total']); 998 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 999 1000 // Search by block (use news_items default block). Should fail (only admins allowed). 1001 $this->expectException('required_capability_exception'); 1002 $results = core_course_external::search_courses('blocklist', $blockid); 1003 } 1004 1005 /** 1006 * Test searching for courses returns custom field data 1007 */ 1008 public function test_search_courses_customfields(): void { 1009 $this->resetAfterTest(); 1010 $this->setAdminUser(); 1011 1012 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 1013 $datefield = $this->getDataGenerator()->create_custom_field([ 1014 'categoryid' => $fieldcategory->get('id'), 1015 'shortname' => 'mydate', 1016 'name' => 'My date', 1017 'type' => 'date', 1018 ]); 1019 1020 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 1021 [ 1022 'shortname' => $datefield->get('shortname'), 1023 'value' => 1580389200, // 30/01/2020 13:00 GMT. 1024 ], 1025 ]]); 1026 1027 $result = external_api::clean_returnvalue( 1028 core_course_external::search_courses_returns(), 1029 core_course_external::search_courses('search', $newcourse->shortname) 1030 ); 1031 1032 $this->assertCount(1, $result['courses']); 1033 $course = reset($result['courses']); 1034 1035 $this->assertArrayHasKey('customfields', $course); 1036 $this->assertCount(1, $course['customfields']); 1037 1038 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 1039 $this->assertEquals([ 1040 'name' => $datefield->get('name'), 1041 'shortname' => $datefield->get('shortname'), 1042 'type' => $datefield->get('type'), 1043 'value' => userdate(1580389200), 1044 'valueraw' => 1580389200, 1045 ], reset($course['customfields'])); 1046 } 1047 1048 /** 1049 * Create a course with contents 1050 * @return array A list with the course object and course modules objects 1051 */ 1052 private function prepare_get_course_contents_test() { 1053 global $DB, $CFG; 1054 1055 $CFG->allowstealth = 1; // Allow stealth activities. 1056 $CFG->enablecompletion = true; 1057 // Course with 4 sections (apart from the main section), with completion and not displaying hidden sections. 1058 $course = self::getDataGenerator()->create_course(['numsections' => 4, 'enablecompletion' => 1, 'hiddensections' => 1]); 1059 1060 $forumdescription = 'This is the forum description'; 1061 $forum = $this->getDataGenerator()->create_module('forum', 1062 array('course' => $course->id, 'intro' => $forumdescription, 'trackingtype' => 2), 1063 array('showdescription' => true, 'completion' => COMPLETION_TRACKING_MANUAL)); 1064 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 1065 // Add discussions to the tracking forced forum. 1066 $record = new stdClass(); 1067 $record->course = $course->id; 1068 $record->userid = 0; 1069 $record->forum = $forum->id; 1070 $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1071 $data = $this->getDataGenerator()->create_module('data', 1072 array('assessed' => 1, 'scale' => 100, 'course' => $course->id, 'completion' => 2, 'completionentries' => 3)); 1073 $datacm = get_coursemodule_from_instance('data', $data->id); 1074 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1075 $pagecm = get_coursemodule_from_instance('page', $page->id); 1076 // This is an stealth page (set by visibleoncoursepage). 1077 $pagestealth = $this->getDataGenerator()->create_module('page', array('course' => $course->id, 'visibleoncoursepage' => 0)); 1078 $labeldescription = 'This is a very long label to test if more than 50 characters are returned. 1079 So bla bla bla bla <b>bold bold bold</b> bla bla bla bla.'; 1080 $label = $this->getDataGenerator()->create_module('label', array('course' => $course->id, 1081 'intro' => $labeldescription, 'completion' => COMPLETION_TRACKING_MANUAL)); 1082 $labelcm = get_coursemodule_from_instance('label', $label->id); 1083 $tomorrow = time() + DAYSECS; 1084 // Module with availability restrictions not met. 1085 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '},' 1086 .'{"type":"completion","cm":' . $label->cmid .',"e":1}],"showc":[true,true]}'; 1087 $url = $this->getDataGenerator()->create_module('url', 1088 array('course' => $course->id, 'name' => 'URL: % & $ ../', 'section' => 2, 'display' => RESOURCELIB_DISPLAY_POPUP, 1089 'popupwidth' => 100, 'popupheight' => 100), 1090 array('availability' => $availability)); 1091 $urlcm = get_coursemodule_from_instance('url', $url->id); 1092 // Module for the last section. 1093 $this->getDataGenerator()->create_module('url', 1094 array('course' => $course->id, 'name' => 'URL for last section', 'section' => 3)); 1095 // Module for section 1 with availability restrictions met. 1096 $yesterday = time() - DAYSECS; 1097 $this->getDataGenerator()->create_module('url', 1098 array('course' => $course->id, 'name' => 'URL restrictions met', 'section' => 1), 1099 array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":'. $yesterday .'}],"showc":[true]}')); 1100 1101 // Set the required capabilities by the external function. 1102 $context = context_course::instance($course->id); 1103 $roleid = $this->assignUserCapability('moodle/course:view', $context->id); 1104 $this->assignUserCapability('moodle/course:update', $context->id, $roleid); 1105 $this->assignUserCapability('mod/data:view', $context->id, $roleid); 1106 1107 $conditions = array('course' => $course->id, 'section' => 2); 1108 $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions); 1109 1110 // Add date availability condition not met for section 3. 1111 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '}],"showc":[true]}'; 1112 $DB->set_field('course_sections', 'availability', $availability, 1113 array('course' => $course->id, 'section' => 3)); 1114 1115 // Create resource for last section. 1116 $pageinhiddensection = $this->getDataGenerator()->create_module('page', 1117 array('course' => $course->id, 'name' => 'Page in hidden section', 'section' => 4)); 1118 // Set not visible last section. 1119 $DB->set_field('course_sections', 'visible', 0, 1120 array('course' => $course->id, 'section' => 4)); 1121 1122 $forumcompleteauto = $this->getDataGenerator()->create_module('forum', 1123 array('course' => $course->id, 'intro' => 'forum completion tracking auto', 'trackingtype' => 2), 1124 array('showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC)); 1125 $forumcompleteautocm = get_coursemodule_from_id('forum', $forumcompleteauto->cmid); 1126 $sectionrecord = $DB->get_record('course_sections', $conditions); 1127 // Invalidate the section cache by given section number. 1128 course_modinfo::purge_course_section_cache_by_number($sectionrecord->course, $sectionrecord->section); 1129 rebuild_course_cache($course->id, true, true); 1130 1131 return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm); 1132 } 1133 1134 /** 1135 * Test get_course_contents 1136 */ 1137 public function test_get_course_contents() { 1138 global $CFG; 1139 $this->resetAfterTest(true); 1140 1141 $CFG->forum_allowforcedreadtracking = 1; 1142 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1143 1144 // We first run the test as admin. 1145 $this->setAdminUser(); 1146 $sections = core_course_external::get_course_contents($course->id, array()); 1147 // We need to execute the return values cleaning process to simulate the web service server. 1148 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1149 1150 $modinfo = get_fast_modinfo($course); 1151 $testexecuted = 0; 1152 foreach ($sections[0]['modules'] as $module) { 1153 if ($module['id'] == $forumcm->id and $module['modname'] == 'forum') { 1154 $cm = $modinfo->cms[$forumcm->id]; 1155 $formattedtext = format_text($cm->content, FORMAT_HTML, 1156 array('noclean' => true, 'para' => false, 'filter' => false)); 1157 $this->assertEquals($formattedtext, $module['description']); 1158 $this->assertEquals($forumcm->instance, $module['instance']); 1159 $this->assertEquals(context_module::instance($forumcm->id)->id, $module['contextid']); 1160 $this->assertStringContainsString('1 unread post', $module['afterlink']); 1161 $this->assertFalse($module['noviewlink']); 1162 $this->assertNotEmpty($module['description']); // Module showdescription is on. 1163 $testexecuted = $testexecuted + 2; 1164 } else if ($module['id'] == $labelcm->id and $module['modname'] == 'label') { 1165 $cm = $modinfo->cms[$labelcm->id]; 1166 $formattedtext = format_text($cm->content, FORMAT_HTML, 1167 array('noclean' => true, 'para' => false, 'filter' => false)); 1168 $this->assertEquals($formattedtext, $module['description']); 1169 $this->assertEquals($labelcm->instance, $module['instance']); 1170 $this->assertEquals(context_module::instance($labelcm->id)->id, $module['contextid']); 1171 $this->assertTrue($module['noviewlink']); 1172 $this->assertNotEmpty($module['description']); // Label always prints the description. 1173 $testexecuted = $testexecuted + 1; 1174 } else if ($module['id'] == $datacm->id and $module['modname'] == 'data') { 1175 $this->assertStringContainsString('customcompletionrules', $module['customdata']); 1176 $this->assertFalse($module['noviewlink']); 1177 $this->assertArrayNotHasKey('description', $module); 1178 $testexecuted = $testexecuted + 1; 1179 } 1180 } 1181 foreach ($sections[2]['modules'] as $module) { 1182 if ($module['id'] == $urlcm->id and $module['modname'] == 'url') { 1183 $this->assertStringContainsString('width=100,height=100', $module['onclick']); 1184 $testexecuted = $testexecuted + 1; 1185 } 1186 } 1187 1188 $CFG->forum_allowforcedreadtracking = 0; // Recover original value. 1189 forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests. 1190 1191 $this->assertEquals(5, $testexecuted); 1192 $this->assertEquals(0, $sections[0]['section']); 1193 1194 $this->assertCount(6, $sections[0]['modules']); 1195 $this->assertCount(1, $sections[1]['modules']); 1196 $this->assertCount(1, $sections[2]['modules']); 1197 $this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions. 1198 $this->assertCount(1, $sections[4]['modules']); // One module for the hidden section with a visible activity. 1199 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1200 $this->assertEquals(1, $sections[1]['section']); 1201 $this->assertEquals(2, $sections[2]['section']); 1202 $this->assertEquals(3, $sections[3]['section']); 1203 $this->assertEquals(4, $sections[4]['section']); 1204 $this->assertStringContainsString('<iframe', $sections[2]['summary']); 1205 $this->assertStringContainsString('</iframe>', $sections[2]['summary']); 1206 $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']); 1207 try { 1208 $sections = core_course_external::get_course_contents($course->id, 1209 array(array("name" => "invalid", "value" => 1))); 1210 $this->fail('Exception expected due to invalid option.'); 1211 } catch (moodle_exception $e) { 1212 $this->assertEquals('errorinvalidparam', $e->errorcode); 1213 } 1214 } 1215 1216 1217 /** 1218 * Test get_course_contents as student 1219 */ 1220 public function test_get_course_contents_student() { 1221 global $DB; 1222 $this->resetAfterTest(true); 1223 1224 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1225 1226 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1227 $user = self::getDataGenerator()->create_user(); 1228 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1229 $this->setUser($user); 1230 1231 $sections = core_course_external::get_course_contents($course->id, array()); 1232 // We need to execute the return values cleaning process to simulate the web service server. 1233 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1234 1235 $this->assertCount(4, $sections); // Nothing for the not visible section. 1236 $this->assertCount(6, $sections[0]['modules']); 1237 $this->assertCount(1, $sections[1]['modules']); 1238 $this->assertCount(1, $sections[2]['modules']); 1239 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1240 1241 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1242 $this->assertEquals(1, $sections[1]['section']); 1243 $this->assertEquals(2, $sections[2]['section']); 1244 $this->assertEquals(3, $sections[3]['section']); 1245 // The module with the availability restriction met is returning contents. 1246 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1247 // The module with the availability restriction not met is not returning contents. 1248 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1249 1250 // Now include flag for returning stealth information (fake section). 1251 $sections = core_course_external::get_course_contents($course->id, 1252 array(array("name" => "includestealthmodules", "value" => 1))); 1253 // We need to execute the return values cleaning process to simulate the web service server. 1254 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1255 1256 $this->assertCount(5, $sections); // Include fake section with stealth activities. 1257 $this->assertCount(6, $sections[0]['modules']); 1258 $this->assertCount(1, $sections[1]['modules']); 1259 $this->assertCount(1, $sections[2]['modules']); 1260 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1261 $this->assertCount(1, $sections[4]['modules']); // One stealth module. 1262 $this->assertEquals(-1, $sections[4]['id']); 1263 } 1264 1265 /** 1266 * Test get_course_contents excluding modules 1267 */ 1268 public function test_get_course_contents_excluding_modules() { 1269 $this->resetAfterTest(true); 1270 1271 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1272 1273 // Test exclude modules. 1274 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludemodules", "value" => 1))); 1275 1276 // We need to execute the return values cleaning process to simulate the web service server. 1277 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1278 1279 $this->assertEmpty($sections[0]['modules']); 1280 $this->assertEmpty($sections[1]['modules']); 1281 } 1282 1283 /** 1284 * Test get_course_contents excluding contents 1285 */ 1286 public function test_get_course_contents_excluding_contents() { 1287 $this->resetAfterTest(true); 1288 1289 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1290 1291 // Test exclude modules. 1292 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludecontents", "value" => 1))); 1293 1294 // We need to execute the return values cleaning process to simulate the web service server. 1295 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1296 1297 foreach ($sections as $section) { 1298 foreach ($section['modules'] as $module) { 1299 // Only resources return contents. 1300 if (isset($module['contents'])) { 1301 $this->assertEmpty($module['contents']); 1302 } 1303 } 1304 } 1305 } 1306 1307 /** 1308 * Test get_course_contents filtering by section number 1309 */ 1310 public function test_get_course_contents_section_number() { 1311 $this->resetAfterTest(true); 1312 1313 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1314 1315 // Test exclude modules. 1316 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "sectionnumber", "value" => 0))); 1317 1318 // We need to execute the return values cleaning process to simulate the web service server. 1319 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1320 1321 $this->assertCount(1, $sections); 1322 $this->assertCount(6, $sections[0]['modules']); 1323 } 1324 1325 /** 1326 * Test get_course_contents filtering by cmid 1327 */ 1328 public function test_get_course_contents_cmid() { 1329 $this->resetAfterTest(true); 1330 1331 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1332 1333 // Test exclude modules. 1334 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "cmid", "value" => $forumcm->id))); 1335 1336 // We need to execute the return values cleaning process to simulate the web service server. 1337 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1338 1339 $this->assertCount(4, $sections); 1340 $this->assertCount(1, $sections[0]['modules']); 1341 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1342 } 1343 1344 1345 /** 1346 * Test get_course_contents filtering by cmid and section 1347 */ 1348 public function test_get_course_contents_section_cmid() { 1349 $this->resetAfterTest(true); 1350 1351 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1352 1353 // Test exclude modules. 1354 $sections = core_course_external::get_course_contents($course->id, array( 1355 array("name" => "cmid", "value" => $forumcm->id), 1356 array("name" => "sectionnumber", "value" => 0) 1357 )); 1358 1359 // We need to execute the return values cleaning process to simulate the web service server. 1360 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1361 1362 $this->assertCount(1, $sections); 1363 $this->assertCount(1, $sections[0]['modules']); 1364 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1365 } 1366 1367 /** 1368 * Test get_course_contents filtering by modname 1369 */ 1370 public function test_get_course_contents_modname() { 1371 $this->resetAfterTest(true); 1372 1373 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1374 1375 // Test exclude modules. 1376 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "modname", "value" => "forum"))); 1377 1378 // We need to execute the return values cleaning process to simulate the web service server. 1379 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1380 1381 $this->assertCount(4, $sections); 1382 $this->assertCount(2, $sections[0]['modules']); 1383 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1384 } 1385 1386 /** 1387 * Test get_course_contents filtering by modname 1388 */ 1389 public function test_get_course_contents_modid() { 1390 $this->resetAfterTest(true); 1391 1392 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1393 1394 // Test exclude modules. 1395 $sections = core_course_external::get_course_contents($course->id, array( 1396 array("name" => "modname", "value" => "page"), 1397 array("name" => "modid", "value" => $pagecm->instance), 1398 )); 1399 1400 // We need to execute the return values cleaning process to simulate the web service server. 1401 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1402 1403 $this->assertCount(4, $sections); 1404 $this->assertCount(1, $sections[0]['modules']); 1405 $this->assertEquals("page", $sections[0]['modules'][0]["modname"]); 1406 $this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]); 1407 } 1408 1409 /** 1410 * Test get_course_contents returns downloadcontent value. 1411 */ 1412 public function test_get_course_contents_downloadcontent() { 1413 $this->resetAfterTest(); 1414 1415 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1416 1417 // Test exclude modules. 1418 $sections = core_course_external::get_course_contents($course->id, [ 1419 ['name' => 'modname', 'value' => 'page'], 1420 ['name' => 'modid', 'value' => $pagecm->instance] 1421 ]); 1422 1423 // We need to execute the return values cleaning process to simulate the web service server. 1424 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1425 $this->assertCount(1, $sections[0]['modules']); 1426 $this->assertEquals('page', $sections[0]['modules'][0]['modname']); 1427 $this->assertEquals($pagecm->downloadcontent, $sections[0]['modules'][0]['downloadcontent']); 1428 $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $sections[0]['modules'][0]['downloadcontent']); 1429 } 1430 1431 /** 1432 * Test get course contents completion manual 1433 */ 1434 public function test_get_course_contents_completion_manual() { 1435 global $CFG; 1436 $this->resetAfterTest(true); 1437 1438 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) = 1439 $this->prepare_get_course_contents_test(); 1440 availability_completion\condition::wipe_static_cache(); 1441 1442 // Test activity not completed yet. 1443 $result = core_course_external::get_course_contents($course->id, array( 1444 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1445 // We need to execute the return values cleaning process to simulate the web service server. 1446 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1447 1448 $completiondata = $result[0]['modules'][0]["completiondata"]; 1449 $this->assertCount(1, $result[0]['modules']); 1450 $this->assertEquals("forum", $result[0]['modules'][0]["modname"]); 1451 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1452 $this->assertEquals(0, $completiondata['state']); 1453 $this->assertEquals(0, $completiondata['timecompleted']); 1454 $this->assertEmpty($completiondata['overrideby']); 1455 $this->assertFalse($completiondata['valueused']); 1456 $this->assertTrue($completiondata['hascompletion']); 1457 $this->assertFalse($completiondata['isautomatic']); 1458 $this->assertFalse($completiondata['istrackeduser']); 1459 $this->assertTrue($completiondata['uservisible']); 1460 1461 // Set activity completed. 1462 core_completion_external::update_activity_completion_status_manually($forumcm->id, true); 1463 1464 $result = core_course_external::get_course_contents($course->id, array( 1465 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1466 // We need to execute the return values cleaning process to simulate the web service server. 1467 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1468 1469 $this->assertEquals(COMPLETION_COMPLETE, $result[0]['modules'][0]["completiondata"]['state']); 1470 $this->assertNotEmpty($result[0]['modules'][0]["completiondata"]['timecompleted']); 1471 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']); 1472 1473 // Test activity with completion value that is used in an availability condition. 1474 $result = core_course_external::get_course_contents($course->id, array( 1475 array("name" => "modname", "value" => "label"), array("name" => "modid", "value" => $labelcm->instance))); 1476 // We need to execute the return values cleaning process to simulate the web service server. 1477 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1478 1479 $completiondata = $result[0]['modules'][0]["completiondata"]; 1480 $this->assertCount(1, $result[0]['modules']); 1481 $this->assertEquals("label", $result[0]['modules'][0]["modname"]); 1482 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1483 $this->assertEquals(0, $completiondata['state']); 1484 $this->assertEquals(0, $completiondata['timecompleted']); 1485 $this->assertEmpty($completiondata['overrideby']); 1486 $this->assertTrue($completiondata['valueused']); 1487 $this->assertTrue($completiondata['hascompletion']); 1488 $this->assertFalse($completiondata['isautomatic']); 1489 $this->assertFalse($completiondata['istrackeduser']); 1490 $this->assertTrue($completiondata['uservisible']); 1491 1492 // Disable completion. 1493 $CFG->enablecompletion = 0; 1494 $result = core_course_external::get_course_contents($course->id, array( 1495 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1496 // We need to execute the return values cleaning process to simulate the web service server. 1497 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1498 1499 $this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]); 1500 } 1501 1502 /** 1503 * Test get course contents completion auto 1504 */ 1505 public function test_get_course_contents_completion_auto() { 1506 global $CFG; 1507 $this->resetAfterTest(true); 1508 1509 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) = 1510 $this->prepare_get_course_contents_test(); 1511 availability_completion\condition::wipe_static_cache(); 1512 1513 // Test activity not completed yet. 1514 $result = core_course_external::get_course_contents($course->id, [ 1515 [ 1516 "name" => "modname", 1517 "value" => "forum" 1518 ], 1519 [ 1520 "name" => "modid", 1521 "value" => $forumcompleteautocm->instance 1522 ] 1523 ]); 1524 // We need to execute the return values cleaning process to simulate the web service server. 1525 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1526 1527 $forummod = $result[0]['modules'][0]; 1528 $completiondata = $forummod["completiondata"]; 1529 $this->assertCount(1, $result[0]['modules']); 1530 $this->assertEquals("forum", $forummod["modname"]); 1531 $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $forummod["completion"]); 1532 $this->assertEquals(0, $completiondata['state']); 1533 $this->assertEquals(0, $completiondata['timecompleted']); 1534 $this->assertEmpty($completiondata['overrideby']); 1535 $this->assertFalse($completiondata['valueused']); 1536 $this->assertTrue($completiondata['hascompletion']); 1537 $this->assertTrue($completiondata['isautomatic']); 1538 $this->assertFalse($completiondata['istrackeduser']); 1539 $this->assertTrue($completiondata['uservisible']); 1540 $this->assertCount(1, $completiondata['details']); 1541 } 1542 1543 /** 1544 * Test mimetype is returned for resources with showtype set. 1545 */ 1546 public function test_get_course_contents_including_mimetype() { 1547 $this->resetAfterTest(true); 1548 1549 $this->setAdminUser(); 1550 $course = self::getDataGenerator()->create_course(); 1551 1552 $record = new stdClass(); 1553 $record->course = $course->id; 1554 $record->showtype = 1; 1555 $resource = self::getDataGenerator()->create_module('resource', $record); 1556 1557 $result = core_course_external::get_course_contents($course->id); 1558 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1559 $this->assertCount(1, $result[0]['modules']); // One module, first section. 1560 $customdata = json_decode($result[0]['modules'][0]['customdata']); 1561 $displayoptions = unserialize($customdata->displayoptions); 1562 $this->assertEquals('text/plain', $displayoptions['filedetails']['mimetype']); 1563 } 1564 1565 /** 1566 * Test contents info is returned. 1567 */ 1568 public function test_get_course_contents_contentsinfo() { 1569 global $USER; 1570 1571 $this->resetAfterTest(true); 1572 $this->setAdminUser(); 1573 $timenow = time(); 1574 1575 $course = self::getDataGenerator()->create_course(); 1576 1577 $record = new stdClass(); 1578 $record->course = $course->id; 1579 // One resource with one file. 1580 $resource1 = self::getDataGenerator()->create_module('resource', $record); 1581 1582 // More type of files. 1583 $record->files = file_get_unused_draft_itemid(); 1584 $usercontext = context_user::instance($USER->id); 1585 $extensions = array('txt', 'png', 'pdf'); 1586 $fs = get_file_storage(); 1587 foreach ($extensions as $key => $extension) { 1588 // Add actual file there. 1589 $filerecord = array('component' => 'user', 'filearea' => 'draft', 1590 'contextid' => $usercontext->id, 'itemid' => $record->files, 1591 'filename' => 'resource' . $key . '.' . $extension, 'filepath' => '/'); 1592 $fs->create_file_from_string($filerecord, 'Test resource ' . $key . ' file'); 1593 } 1594 1595 // Create file reference. 1596 $repos = repository::get_instances(array('type' => 'user')); 1597 $userrepository = reset($repos); 1598 1599 // Create a user private file. 1600 $userfilerecord = new stdClass; 1601 $userfilerecord->contextid = $usercontext->id; 1602 $userfilerecord->component = 'user'; 1603 $userfilerecord->filearea = 'private'; 1604 $userfilerecord->itemid = 0; 1605 $userfilerecord->filepath = '/'; 1606 $userfilerecord->filename = 'userfile.txt'; 1607 $userfilerecord->source = 'test'; 1608 $userfile = $fs->create_file_from_string($userfilerecord, 'User file content'); 1609 $userfileref = $fs->pack_reference($userfilerecord); 1610 1611 // Clone latest "normal" file. 1612 $filerefrecord = clone (object) $filerecord; 1613 $filerefrecord->filename = 'testref.txt'; 1614 $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref); 1615 // Set main file pointing to the file reference. 1616 file_set_sortorder($usercontext->id, 'user', 'draft', $record->files, $filerefrecord->filepath, 1617 $filerefrecord->filename, 1); 1618 1619 // Once the reference has been created, create the file resource. 1620 $resource2 = self::getDataGenerator()->create_module('resource', $record); 1621 1622 $result = core_course_external::get_course_contents($course->id); 1623 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1624 $this->assertCount(2, $result[0]['modules']); 1625 foreach ($result[0]['modules'] as $module) { 1626 if ($module['instance'] == $resource1->id) { 1627 $this->assertEquals(1, $module['contentsinfo']['filescount']); 1628 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1629 $this->assertEquals($module['contents'][0]['filesize'], $module['contentsinfo']['filessize']); 1630 $this->assertEquals(array('text/plain'), $module['contentsinfo']['mimetypes']); 1631 } else { 1632 $this->assertEquals(count($extensions) + 1, $module['contentsinfo']['filescount']); 1633 $filessize = $module['contents'][0]['filesize'] + $module['contents'][1]['filesize'] + 1634 $module['contents'][2]['filesize'] + $module['contents'][3]['filesize']; 1635 $this->assertEquals($filessize, $module['contentsinfo']['filessize']); 1636 $this->assertEquals('user', $module['contentsinfo']['repositorytype']); 1637 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1638 $this->assertEquals(array('text/plain', 'image/png', 'application/pdf'), $module['contentsinfo']['mimetypes']); 1639 } 1640 } 1641 } 1642 1643 /** 1644 * Test get_course_contents when hidden sections are displayed. 1645 */ 1646 public function test_get_course_contents_hiddensections() { 1647 global $DB; 1648 $this->resetAfterTest(true); 1649 1650 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1651 // Force returning hidden sections. 1652 $course->hiddensections = 0; 1653 update_course($course); 1654 1655 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1656 $user = self::getDataGenerator()->create_user(); 1657 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1658 $this->setUser($user); 1659 1660 $sections = core_course_external::get_course_contents($course->id, array()); 1661 // We need to execute the return values cleaning process to simulate the web service server. 1662 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1663 1664 $this->assertCount(5, $sections); // All the sections, including the "not visible" one. 1665 $this->assertCount(6, $sections[0]['modules']); 1666 $this->assertCount(1, $sections[1]['modules']); 1667 $this->assertCount(1, $sections[2]['modules']); 1668 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1669 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1670 1671 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1672 $this->assertEquals(1, $sections[1]['section']); 1673 $this->assertEquals(2, $sections[2]['section']); 1674 $this->assertEquals(3, $sections[3]['section']); 1675 // The module with the availability restriction met is returning contents. 1676 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1677 // The module with the availability restriction not met is not returning contents. 1678 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1679 1680 // Now include flag for returning stealth information (fake section). 1681 $sections = core_course_external::get_course_contents($course->id, 1682 array(array("name" => "includestealthmodules", "value" => 1))); 1683 // We need to execute the return values cleaning process to simulate the web service server. 1684 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1685 1686 $this->assertCount(6, $sections); // Include fake section with stealth activities. 1687 $this->assertCount(6, $sections[0]['modules']); 1688 $this->assertCount(1, $sections[1]['modules']); 1689 $this->assertCount(1, $sections[2]['modules']); 1690 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1691 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1692 $this->assertCount(1, $sections[5]['modules']); // One stealth module. 1693 $this->assertEquals(-1, $sections[5]['id']); 1694 } 1695 1696 /** 1697 * Test get course contents dates. 1698 */ 1699 public function test_get_course_contents_dates() { 1700 $this->resetAfterTest(true); 1701 1702 $this->setAdminUser(); 1703 set_config('enablecourserelativedates', 1); 1704 1705 // Course with just main section. 1706 $timenow = time(); 1707 $course = self::getDataGenerator()->create_course( 1708 ['numsections' => 0, 'relativedatesmode' => true, 'startdate' => $timenow - DAYSECS]); 1709 1710 $teacher = self::getDataGenerator()->create_user(); 1711 self::getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); 1712 1713 $this->setUser($teacher); 1714 1715 // Create resource (empty dates). 1716 $resource = self::getDataGenerator()->create_module('resource', ['course' => $course->id]); 1717 // Create activities with dates. 1718 $resource = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'duedate' => $timenow]); 1719 $resource = self::getDataGenerator()->create_module('choice', 1720 ['course' => $course->id, 'timeopen' => $timenow, 'timeclose' => $timenow + DAYSECS]); 1721 $resource = self::getDataGenerator()->create_module('assign', 1722 ['course' => $course->id, 'allowsubmissionsfromdate' => $timenow]); 1723 1724 $result = core_course_external::get_course_contents($course->id); 1725 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1726 1727 foreach ($result[0]['modules'] as $module) { 1728 if ($module['modname'] == 'resource') { 1729 $this->assertEmpty($module['dates']); 1730 } else if ($module['modname'] == 'forum') { 1731 $this->assertCount(1, $module['dates']); 1732 $this->assertEquals('duedate', $module['dates'][0]['dataid']); 1733 $this->assertEquals($timenow, $module['dates'][0]['timestamp']); 1734 } else if ($module['modname'] == 'choice') { 1735 $this->assertCount(2, $module['dates']); 1736 $this->assertEquals('timeopen', $module['dates'][0]['dataid']); 1737 $this->assertEquals($timenow, $module['dates'][0]['timestamp']); 1738 $this->assertEquals('timeclose', $module['dates'][1]['dataid']); 1739 $this->assertEquals($timenow + DAYSECS, $module['dates'][1]['timestamp']); 1740 } else if ($module['modname'] == 'assign') { 1741 $this->assertCount(1, $module['dates']); 1742 $this->assertEquals('allowsubmissionsfromdate', $module['dates'][0]['dataid']); 1743 $this->assertEquals($timenow, $module['dates'][0]['timestamp']); 1744 $this->assertEquals($course->startdate, $module['dates'][0]['relativeto']); 1745 } 1746 } 1747 } 1748 1749 /** 1750 * Test duplicate_course 1751 */ 1752 public function test_duplicate_course() { 1753 $this->resetAfterTest(true); 1754 1755 // Create one course with three modules. 1756 $course = self::getDataGenerator()->create_course(); 1757 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id)); 1758 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 1759 $forumcontext = context_module::instance($forum->cmid); 1760 $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id)); 1761 $datacontext = context_module::instance($data->cmid); 1762 $datacm = get_coursemodule_from_instance('page', $data->id); 1763 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id)); 1764 $pagecontext = context_module::instance($page->cmid); 1765 $pagecm = get_coursemodule_from_instance('page', $page->id); 1766 1767 // Set the required capabilities by the external function. 1768 $coursecontext = context_course::instance($course->id); 1769 $categorycontext = context_coursecat::instance($course->category); 1770 $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id); 1771 $this->assignUserCapability('moodle/course:view', $categorycontext->id, $roleid); 1772 $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id, $roleid); 1773 $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id, $roleid); 1774 $this->assignUserCapability('moodle/backup:configure', $coursecontext->id, $roleid); 1775 // Optional capabilities to copy user data. 1776 $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id, $roleid); 1777 $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id, $roleid); 1778 1779 $newcourse['fullname'] = 'Course duplicate'; 1780 $newcourse['shortname'] = 'courseduplicate'; 1781 $newcourse['categoryid'] = $course->category; 1782 $newcourse['visible'] = true; 1783 $newcourse['options'][] = array('name' => 'users', 'value' => true); 1784 1785 $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'], 1786 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']); 1787 1788 // We need to execute the return values cleaning process to simulate the web service server. 1789 $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate); 1790 1791 // Check that the course has been duplicated. 1792 $this->assertEquals($newcourse['shortname'], $duplicate['shortname']); 1793 } 1794 1795 /** 1796 * Test update_courses 1797 */ 1798 public function test_update_courses() { 1799 global $DB, $CFG, $USER, $COURSE; 1800 1801 // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this 1802 // trick because we are both updating and getting (for testing) course information 1803 // in the same request and core_course_external::update_courses() 1804 // is overwriting $COURSE all over the time with OLD values, so later 1805 // use of get_course() fetches those OLD values instead of the updated ones. 1806 // See MDL-39723 for more info. 1807 $origcourse = clone($COURSE); 1808 1809 $this->resetAfterTest(true); 1810 1811 // Set the required capabilities by the external function. 1812 $contextid = context_system::instance()->id; 1813 $roleid = $this->assignUserCapability('moodle/course:update', $contextid); 1814 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1815 $this->assignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 1816 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1817 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1818 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1819 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1820 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 1821 $this->assignUserCapability('moodle/course:viewhiddencourses', $contextid, $roleid); 1822 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid); 1823 1824 // Create category and courses. 1825 $category1 = self::getDataGenerator()->create_category(); 1826 $category2 = self::getDataGenerator()->create_category(); 1827 1828 $originalcourse1 = self::getDataGenerator()->create_course(); 1829 self::getDataGenerator()->enrol_user($USER->id, $originalcourse1->id, $roleid); 1830 1831 $originalcourse2 = self::getDataGenerator()->create_course(); 1832 self::getDataGenerator()->enrol_user($USER->id, $originalcourse2->id, $roleid); 1833 1834 // Course with custom fields. 1835 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 1836 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 1837 'categoryid' => $fieldcategory->get('id'), 1838 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL, 'locked' => 1]]; 1839 $field = self::getDataGenerator()->create_custom_field($customfield); 1840 1841 $originalcourse3 = self::getDataGenerator()->create_course(['customfield_test' => 'Test value']); 1842 self::getDataGenerator()->enrol_user($USER->id, $originalcourse3->id, $roleid); 1843 1844 // Course values to be updated. 1845 $course1['id'] = $originalcourse1->id; 1846 $course1['fullname'] = 'Updated test course 1'; 1847 $course1['shortname'] = 'Udestedtestcourse1'; 1848 $course1['categoryid'] = $category1->id; 1849 1850 $course2['id'] = $originalcourse2->id; 1851 $course2['fullname'] = 'Updated test course 2'; 1852 $course2['shortname'] = 'Updestedtestcourse2'; 1853 $course2['categoryid'] = $category2->id; 1854 $course2['idnumber'] = 'Updatedidnumber2'; 1855 $course2['summary'] = 'Updaated description for course 2'; 1856 $course2['summaryformat'] = FORMAT_HTML; 1857 $course2['format'] = 'topics'; 1858 $course2['showgrades'] = 1; 1859 $course2['newsitems'] = 3; 1860 $course2['startdate'] = 1420092000; // 01/01/2015. 1861 $course2['enddate'] = 1422669600; // 01/31/2015. 1862 $course2['maxbytes'] = 100000; 1863 $course2['showreports'] = 1; 1864 $course2['visible'] = 0; 1865 $course2['hiddensections'] = 0; 1866 $course2['groupmode'] = 0; 1867 $course2['groupmodeforce'] = 0; 1868 $course2['defaultgroupingid'] = 0; 1869 $course2['enablecompletion'] = 1; 1870 $course2['lang'] = 'en'; 1871 $course2['forcetheme'] = 'classic'; 1872 1873 $course3['id'] = $originalcourse3->id; 1874 $updatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'Updated test value']; 1875 $course3['customfields'] = [$updatedcustomfieldvalue]; 1876 $courses = array($course1, $course2, $course3); 1877 1878 $updatedcoursewarnings = core_course_external::update_courses($courses); 1879 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1880 $updatedcoursewarnings); 1881 $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line. 1882 1883 // Check that right number of courses were created. 1884 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1885 1886 // Check that the courses were correctly created. 1887 foreach ($courses as $course) { 1888 $courseinfo = course_get_format($course['id'])->get_course(); 1889 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course['id']); 1890 if ($course['id'] == $course2['id']) { 1891 $this->assertEquals($course2['fullname'], $courseinfo->fullname); 1892 $this->assertEquals($course2['shortname'], $courseinfo->shortname); 1893 $this->assertEquals($course2['categoryid'], $courseinfo->category); 1894 $this->assertEquals($course2['idnumber'], $courseinfo->idnumber); 1895 $this->assertEquals($course2['summary'], $courseinfo->summary); 1896 $this->assertEquals($course2['summaryformat'], $courseinfo->summaryformat); 1897 $this->assertEquals($course2['format'], $courseinfo->format); 1898 $this->assertEquals($course2['showgrades'], $courseinfo->showgrades); 1899 $this->assertEquals($course2['newsitems'], $courseinfo->newsitems); 1900 $this->assertEquals($course2['startdate'], $courseinfo->startdate); 1901 $this->assertEquals($course2['enddate'], $courseinfo->enddate); 1902 $this->assertEquals($course2['maxbytes'], $courseinfo->maxbytes); 1903 $this->assertEquals($course2['showreports'], $courseinfo->showreports); 1904 $this->assertEquals($course2['visible'], $courseinfo->visible); 1905 $this->assertEquals($course2['hiddensections'], $courseinfo->hiddensections); 1906 $this->assertEquals($course2['groupmode'], $courseinfo->groupmode); 1907 $this->assertEquals($course2['groupmodeforce'], $courseinfo->groupmodeforce); 1908 $this->assertEquals($course2['defaultgroupingid'], $courseinfo->defaultgroupingid); 1909 $this->assertEquals($course2['lang'], $courseinfo->lang); 1910 1911 if (!empty($CFG->allowcoursethemes)) { 1912 $this->assertEquals($course2['forcetheme'], $courseinfo->theme); 1913 } 1914 1915 $this->assertEquals($course2['enablecompletion'], $courseinfo->enablecompletion); 1916 $this->assertEquals(['test' => null], (array)$customfields); 1917 } else if ($course['id'] == $course1['id']) { 1918 $this->assertEquals($course1['fullname'], $courseinfo->fullname); 1919 $this->assertEquals($course1['shortname'], $courseinfo->shortname); 1920 $this->assertEquals($course1['categoryid'], $courseinfo->category); 1921 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1922 $this->assertEquals('topics', $courseinfo->format); 1923 $this->assertEquals(5, course_get_format($course['id'])->get_last_section_number()); 1924 $this->assertEquals(0, $courseinfo->newsitems); 1925 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1926 $this->assertEquals(['test' => null], (array)$customfields); 1927 } else if ($course['id'] == $course3['id']) { 1928 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 1929 } else { 1930 throw new moodle_exception('Unexpected shortname'); 1931 } 1932 } 1933 1934 $courses = array($course1); 1935 // Try update course without update capability. 1936 $user = self::getDataGenerator()->create_user(); 1937 $this->setUser($user); 1938 $this->unassignUserCapability('moodle/course:update', $contextid, $roleid); 1939 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1940 $updatedcoursewarnings = core_course_external::update_courses($courses); 1941 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1942 $updatedcoursewarnings); 1943 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1944 1945 // Try update course category without capability. 1946 $this->assignUserCapability('moodle/course:update', $contextid, $roleid); 1947 $this->unassignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1948 $user = self::getDataGenerator()->create_user(); 1949 $this->setUser($user); 1950 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1951 $course1['categoryid'] = $category2->id; 1952 $courses = array($course1); 1953 $updatedcoursewarnings = core_course_external::update_courses($courses); 1954 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1955 $updatedcoursewarnings); 1956 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1957 1958 // Try update course fullname without capability. 1959 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1960 $this->unassignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1961 $user = self::getDataGenerator()->create_user(); 1962 $this->setUser($user); 1963 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1964 $updatedcoursewarnings = core_course_external::update_courses($courses); 1965 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1966 $updatedcoursewarnings); 1967 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1968 $course1['fullname'] = 'Testing fullname without permission'; 1969 $courses = array($course1); 1970 $updatedcoursewarnings = core_course_external::update_courses($courses); 1971 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1972 $updatedcoursewarnings); 1973 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1974 1975 // Try update course shortname without capability. 1976 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1977 $this->unassignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1978 $user = self::getDataGenerator()->create_user(); 1979 $this->setUser($user); 1980 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1981 $updatedcoursewarnings = core_course_external::update_courses($courses); 1982 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1983 $updatedcoursewarnings); 1984 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1985 $course1['shortname'] = 'Testing shortname without permission'; 1986 $courses = array($course1); 1987 $updatedcoursewarnings = core_course_external::update_courses($courses); 1988 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1989 $updatedcoursewarnings); 1990 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1991 1992 // Try update course idnumber without capability. 1993 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1994 $this->unassignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1995 $user = self::getDataGenerator()->create_user(); 1996 $this->setUser($user); 1997 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1998 $updatedcoursewarnings = core_course_external::update_courses($courses); 1999 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2000 $updatedcoursewarnings); 2001 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 2002 $course1['idnumber'] = 'NEWIDNUMBER'; 2003 $courses = array($course1); 2004 $updatedcoursewarnings = core_course_external::update_courses($courses); 2005 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2006 $updatedcoursewarnings); 2007 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 2008 2009 // Try update course summary without capability. 2010 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 2011 $this->unassignUserCapability('moodle/course:changesummary', $contextid, $roleid); 2012 $user = self::getDataGenerator()->create_user(); 2013 $this->setUser($user); 2014 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 2015 $updatedcoursewarnings = core_course_external::update_courses($courses); 2016 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2017 $updatedcoursewarnings); 2018 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 2019 $course1['summary'] = 'New summary'; 2020 $courses = array($course1); 2021 $updatedcoursewarnings = core_course_external::update_courses($courses); 2022 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2023 $updatedcoursewarnings); 2024 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 2025 2026 // Try update course with invalid summary format. 2027 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 2028 $user = self::getDataGenerator()->create_user(); 2029 $this->setUser($user); 2030 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 2031 $updatedcoursewarnings = core_course_external::update_courses($courses); 2032 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2033 $updatedcoursewarnings); 2034 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 2035 $course1['summaryformat'] = 10; 2036 $courses = array($course1); 2037 $updatedcoursewarnings = core_course_external::update_courses($courses); 2038 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2039 $updatedcoursewarnings); 2040 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 2041 2042 // Try update course visibility without capability. 2043 $this->unassignUserCapability('moodle/course:visibility', $contextid, $roleid); 2044 $user = self::getDataGenerator()->create_user(); 2045 $this->setUser($user); 2046 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 2047 $course1['summaryformat'] = FORMAT_MOODLE; 2048 $courses = array($course1); 2049 $updatedcoursewarnings = core_course_external::update_courses($courses); 2050 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2051 $updatedcoursewarnings); 2052 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 2053 $course1['visible'] = 0; 2054 $courses = array($course1); 2055 $updatedcoursewarnings = core_course_external::update_courses($courses); 2056 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 2057 $updatedcoursewarnings); 2058 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 2059 2060 // Try update course custom fields without capability. 2061 $this->unassignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 2062 $user = self::getDataGenerator()->create_user(); 2063 $this->setUser($user); 2064 self::getDataGenerator()->enrol_user($user->id, $course3['id'], $roleid); 2065 2066 $newupdatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'New updated value']; 2067 $course3['customfields'] = [$newupdatedcustomfieldvalue]; 2068 2069 core_course_external::update_courses([$course3]); 2070 2071 // Custom field was not updated. 2072 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course3['id']); 2073 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 2074 } 2075 2076 /** 2077 * Test delete course_module. 2078 */ 2079 public function test_delete_modules() { 2080 global $DB; 2081 2082 // Ensure we reset the data after this test. 2083 $this->resetAfterTest(true); 2084 2085 // Create a user. 2086 $user = self::getDataGenerator()->create_user(); 2087 2088 // Set the tests to run as the user. 2089 self::setUser($user); 2090 2091 // Create a course to add the modules. 2092 $course = self::getDataGenerator()->create_course(); 2093 2094 // Create two test modules. 2095 $record = new stdClass(); 2096 $record->course = $course->id; 2097 $module1 = self::getDataGenerator()->create_module('forum', $record); 2098 $module2 = self::getDataGenerator()->create_module('assign', $record); 2099 2100 // Check the forum was correctly created. 2101 $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id))); 2102 2103 // Check the assignment was correctly created. 2104 $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id))); 2105 2106 // Check data exists in the course modules table. 2107 $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 2108 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 2109 2110 // Enrol the user in the course. 2111 $enrol = enrol_get_plugin('manual'); 2112 $enrolinstances = enrol_get_instances($course->id, true); 2113 foreach ($enrolinstances as $courseenrolinstance) { 2114 if ($courseenrolinstance->enrol == "manual") { 2115 $instance = $courseenrolinstance; 2116 break; 2117 } 2118 } 2119 $enrol->enrol_user($instance, $user->id); 2120 2121 // Assign capabilities to delete module 1. 2122 $modcontext = context_module::instance($module1->cmid); 2123 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id); 2124 2125 // Assign capabilities to delete module 2. 2126 $modcontext = context_module::instance($module2->cmid); 2127 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 2128 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole); 2129 2130 // Deleting these module instances. 2131 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2132 2133 // Check the forum was deleted. 2134 $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id))); 2135 2136 // Check the assignment was deleted. 2137 $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id))); 2138 2139 // Check we retrieve no data in the course modules table. 2140 $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 2141 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 2142 2143 // Call with non-existent course module id and ensure exception thrown. 2144 try { 2145 core_course_external::delete_modules(array('1337')); 2146 $this->fail('Exception expected due to missing course module.'); 2147 } catch (dml_missing_record_exception $e) { 2148 $this->assertEquals('invalidcoursemodule', $e->errorcode); 2149 } 2150 2151 // Create two modules. 2152 $module1 = self::getDataGenerator()->create_module('forum', $record); 2153 $module2 = self::getDataGenerator()->create_module('assign', $record); 2154 2155 // Since these modules were recreated the user will not have capabilities 2156 // to delete them, ensure exception is thrown if they try. 2157 try { 2158 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2159 $this->fail('Exception expected due to missing capability.'); 2160 } catch (moodle_exception $e) { 2161 $this->assertEquals('nopermissions', $e->errorcode); 2162 } 2163 2164 // Unenrol user from the course. 2165 $enrol->unenrol_user($instance, $user->id); 2166 2167 // Try and delete modules from the course the user was unenrolled in, make sure exception thrown. 2168 try { 2169 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2170 $this->fail('Exception expected due to being unenrolled from the course.'); 2171 } catch (moodle_exception $e) { 2172 $this->assertEquals('requireloginerror', $e->errorcode); 2173 } 2174 } 2175 2176 /** 2177 * Test import_course into an empty course 2178 */ 2179 public function test_import_course_empty() { 2180 global $USER; 2181 2182 $this->resetAfterTest(true); 2183 2184 $course1 = self::getDataGenerator()->create_course(); 2185 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id, 'name' => 'Forum test')); 2186 $page = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 'name' => 'Page test')); 2187 2188 $course2 = self::getDataGenerator()->create_course(); 2189 2190 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2191 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2192 2193 // Verify the state of the courses before we do the import. 2194 $this->assertCount(2, $course1cms); 2195 $this->assertEmpty($course2cms); 2196 2197 // Setup the user to run the operation (ugly hack because validate_context() will 2198 // fail as the email is not set by $this->setAdminUser()). 2199 $this->setAdminUser(); 2200 $USER->email = 'emailtopass@example.com'; 2201 2202 // Import from course1 to course2. 2203 core_course_external::import_course($course1->id, $course2->id, 0); 2204 2205 // Verify that now we have two modules in both courses. 2206 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2207 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2208 $this->assertCount(2, $course1cms); 2209 $this->assertCount(2, $course2cms); 2210 2211 // Verify that the names transfered across correctly. 2212 foreach ($course2cms as $cm) { 2213 if ($cm->modname === 'page') { 2214 $this->assertEquals($cm->name, $page->name); 2215 } else if ($cm->modname === 'forum') { 2216 $this->assertEquals($cm->name, $forum->name); 2217 } else { 2218 $this->fail('Unknown CM found.'); 2219 } 2220 } 2221 } 2222 2223 /** 2224 * Test import_course into an filled course 2225 */ 2226 public function test_import_course_filled() { 2227 global $USER; 2228 2229 $this->resetAfterTest(true); 2230 2231 // Add forum and page to course1. 2232 $course1 = self::getDataGenerator()->create_course(); 2233 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2234 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2235 2236 // Add quiz to course 2. 2237 $course2 = self::getDataGenerator()->create_course(); 2238 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2239 2240 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2241 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2242 2243 // Verify the state of the courses before we do the import. 2244 $this->assertCount(2, $course1cms); 2245 $this->assertCount(1, $course2cms); 2246 2247 // Setup the user to run the operation (ugly hack because validate_context() will 2248 // fail as the email is not set by $this->setAdminUser()). 2249 $this->setAdminUser(); 2250 $USER->email = 'emailtopass@example.com'; 2251 2252 // Import from course1 to course2 without deleting content. 2253 core_course_external::import_course($course1->id, $course2->id, 0); 2254 2255 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2256 2257 // Verify that now we have three modules in course2. 2258 $this->assertCount(3, $course2cms); 2259 2260 // Verify that the names transfered across correctly. 2261 foreach ($course2cms as $cm) { 2262 if ($cm->modname === 'page') { 2263 $this->assertEquals($cm->name, $page->name); 2264 } else if ($cm->modname === 'forum') { 2265 $this->assertEquals($cm->name, $forum->name); 2266 } else if ($cm->modname === 'quiz') { 2267 $this->assertEquals($cm->name, $quiz->name); 2268 } else { 2269 $this->fail('Unknown CM found.'); 2270 } 2271 } 2272 } 2273 2274 /** 2275 * Test import_course with only blocks set to backup 2276 */ 2277 public function test_import_course_blocksonly() { 2278 global $USER, $DB; 2279 2280 $this->resetAfterTest(true); 2281 2282 // Add forum and page to course1. 2283 $course1 = self::getDataGenerator()->create_course(); 2284 $course1ctx = context_course::instance($course1->id); 2285 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2286 $block = $this->getDataGenerator()->create_block('online_users', array('parentcontextid' => $course1ctx->id)); 2287 2288 $course2 = self::getDataGenerator()->create_course(); 2289 $course2ctx = context_course::instance($course2->id); 2290 $initialblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2291 $initialcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2292 2293 // Setup the user to run the operation (ugly hack because validate_context() will 2294 // fail as the email is not set by $this->setAdminUser()). 2295 $this->setAdminUser(); 2296 $USER->email = 'emailtopass@example.com'; 2297 2298 // Import from course1 to course2 without deleting content, but excluding 2299 // activities. 2300 $options = array( 2301 array('name' => 'activities', 'value' => 0), 2302 array('name' => 'blocks', 'value' => 1), 2303 array('name' => 'filters', 'value' => 0), 2304 ); 2305 2306 core_course_external::import_course($course1->id, $course2->id, 0, $options); 2307 2308 $newcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2309 $newblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2310 // Check that course modules haven't changed, but that blocks have. 2311 $this->assertEquals($initialcmcount, $newcmcount); 2312 $this->assertEquals(($initialblockcount + 1), $newblockcount); 2313 } 2314 2315 /** 2316 * Test import_course into an filled course, deleting content. 2317 */ 2318 public function test_import_course_deletecontent() { 2319 global $USER; 2320 $this->resetAfterTest(true); 2321 2322 // Add forum and page to course1. 2323 $course1 = self::getDataGenerator()->create_course(); 2324 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2325 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2326 2327 // Add quiz to course 2. 2328 $course2 = self::getDataGenerator()->create_course(); 2329 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2330 2331 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2332 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2333 2334 // Verify the state of the courses before we do the import. 2335 $this->assertCount(2, $course1cms); 2336 $this->assertCount(1, $course2cms); 2337 2338 // Setup the user to run the operation (ugly hack because validate_context() will 2339 // fail as the email is not set by $this->setAdminUser()). 2340 $this->setAdminUser(); 2341 $USER->email = 'emailtopass@example.com'; 2342 2343 // Import from course1 to course2, deleting content. 2344 core_course_external::import_course($course1->id, $course2->id, 1); 2345 2346 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2347 2348 // Verify that now we have two modules in course2. 2349 $this->assertCount(2, $course2cms); 2350 2351 // Verify that the course only contains the imported modules. 2352 foreach ($course2cms as $cm) { 2353 if ($cm->modname === 'page') { 2354 $this->assertEquals($cm->name, $page->name); 2355 } else if ($cm->modname === 'forum') { 2356 $this->assertEquals($cm->name, $forum->name); 2357 } else { 2358 $this->fail('Unknown CM found: '.$cm->name); 2359 } 2360 } 2361 } 2362 2363 /** 2364 * Ensure import_course handles incorrect deletecontent option correctly. 2365 */ 2366 public function test_import_course_invalid_deletecontent_option() { 2367 $this->resetAfterTest(true); 2368 2369 $course1 = self::getDataGenerator()->create_course(); 2370 $course2 = self::getDataGenerator()->create_course(); 2371 2372 $this->expectException('moodle_exception'); 2373 $this->expectExceptionMessage(get_string('invalidextparam', 'webservice', -1)); 2374 // Import from course1 to course2, with invalid option 2375 core_course_external::import_course($course1->id, $course2->id, -1);; 2376 } 2377 2378 /** 2379 * Test view_course function 2380 */ 2381 public function test_view_course() { 2382 2383 $this->resetAfterTest(); 2384 2385 // Course without sections. 2386 $course = $this->getDataGenerator()->create_course(array('numsections' => 5), array('createsections' => true)); 2387 $this->setAdminUser(); 2388 2389 // Redirect events to the sink, so we can recover them later. 2390 $sink = $this->redirectEvents(); 2391 2392 $result = core_course_external::view_course($course->id, 1); 2393 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2394 $events = $sink->get_events(); 2395 $event = reset($events); 2396 2397 // Check the event details are correct. 2398 $this->assertInstanceOf('\core\event\course_viewed', $event); 2399 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2400 $this->assertEquals(1, $event->other['coursesectionnumber']); 2401 2402 $result = core_course_external::view_course($course->id); 2403 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2404 $events = $sink->get_events(); 2405 $event = array_pop($events); 2406 $sink->close(); 2407 2408 // Check the event details are correct. 2409 $this->assertInstanceOf('\core\event\course_viewed', $event); 2410 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2411 $this->assertEmpty($event->other); 2412 2413 } 2414 2415 /** 2416 * Test get_course_module 2417 */ 2418 public function test_get_course_module() { 2419 global $DB; 2420 2421 $this->resetAfterTest(true); 2422 2423 $this->setAdminUser(); 2424 $course = self::getDataGenerator()->create_course(['enablecompletion' => 1]); 2425 $record = array( 2426 'course' => $course->id, 2427 'name' => 'First Assignment' 2428 ); 2429 $options = array( 2430 'idnumber' => 'ABC', 2431 'visible' => 0, 2432 'completion' => COMPLETION_TRACKING_AUTOMATIC, 2433 'completiongradeitemnumber' => 0, 2434 'completionpassgrade' => 1, 2435 ); 2436 // Hidden activity. 2437 $assign = self::getDataGenerator()->create_module('assign', $record, $options); 2438 2439 $outcomescale = 'Distinction, Very Good, Good, Pass, Fail'; 2440 2441 // Insert a custom grade scale to be used by an outcome. 2442 $gradescale = new grade_scale(); 2443 $gradescale->name = 'gettcoursemodulescale'; 2444 $gradescale->courseid = $course->id; 2445 $gradescale->userid = 0; 2446 $gradescale->scale = $outcomescale; 2447 $gradescale->description = 'This scale is used to mark standard assignments.'; 2448 $gradescale->insert(); 2449 2450 // Insert an outcome. 2451 $data = new stdClass(); 2452 $data->courseid = $course->id; 2453 $data->fullname = 'Team work'; 2454 $data->shortname = 'Team work'; 2455 $data->scaleid = $gradescale->id; 2456 $outcome = new grade_outcome($data, false); 2457 $outcome->insert(); 2458 2459 $outcomegradeitem = new grade_item(); 2460 $outcomegradeitem->itemname = $outcome->shortname; 2461 $outcomegradeitem->itemtype = 'mod'; 2462 $outcomegradeitem->itemmodule = 'assign'; 2463 $outcomegradeitem->iteminstance = $assign->id; 2464 $outcomegradeitem->outcomeid = $outcome->id; 2465 $outcomegradeitem->cmid = 0; 2466 $outcomegradeitem->courseid = $course->id; 2467 $outcomegradeitem->aggregationcoef = 0; 2468 $outcomegradeitem->itemnumber = 1000; // Outcomes start at 1000. 2469 $outcomegradeitem->gradetype = GRADE_TYPE_SCALE; 2470 $outcomegradeitem->scaleid = $outcome->scaleid; 2471 $outcomegradeitem->insert(); 2472 2473 $assignmentgradeitem = grade_item::fetch( 2474 array( 2475 'itemtype' => 'mod', 2476 'itemmodule' => 'assign', 2477 'iteminstance' => $assign->id, 2478 'itemnumber' => 0, 2479 'courseid' => $course->id 2480 ) 2481 ); 2482 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid); 2483 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder); 2484 2485 // Test admin user can see the complete hidden activity. 2486 $result = core_course_external::get_course_module($assign->cmid); 2487 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2488 2489 $this->assertCount(0, $result['warnings']); 2490 // Test we retrieve all the fields. 2491 $this->assertCount(30, $result['cm']); 2492 $this->assertEquals($record['name'], $result['cm']['name']); 2493 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2494 $this->assertEquals(100, $result['cm']['grade']); 2495 $this->assertEquals(0.0, $result['cm']['gradepass']); 2496 $this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']); 2497 $this->assertEmpty($result['cm']['advancedgrading'][0]['method']); 2498 $this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']); 2499 $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $result['cm']['downloadcontent']); 2500 2501 $student = $this->getDataGenerator()->create_user(); 2502 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2503 2504 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2505 $this->setUser($student); 2506 2507 // The user shouldn't be able to see the activity. 2508 try { 2509 core_course_external::get_course_module($assign->cmid); 2510 $this->fail('Exception expected due to invalid permissions.'); 2511 } catch (moodle_exception $e) { 2512 $this->assertEquals('requireloginerror', $e->errorcode); 2513 } 2514 2515 // Make module visible. 2516 set_coursemodule_visible($assign->cmid, 1); 2517 2518 // Test student user. 2519 $result = core_course_external::get_course_module($assign->cmid); 2520 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2521 2522 $this->assertCount(0, $result['warnings']); 2523 // Test we retrieve only the few files we can see. 2524 $this->assertCount(12, $result['cm']); 2525 $this->assertEquals($assign->cmid, $result['cm']['id']); 2526 $this->assertEquals($course->id, $result['cm']['course']); 2527 $this->assertEquals('assign', $result['cm']['modname']); 2528 $this->assertEquals($assign->id, $result['cm']['instance']); 2529 2530 } 2531 2532 /** 2533 * Test get_course_module_by_instance 2534 */ 2535 public function test_get_course_module_by_instance() { 2536 global $DB; 2537 2538 $this->resetAfterTest(true); 2539 2540 $this->setAdminUser(); 2541 $course = self::getDataGenerator()->create_course(); 2542 $record = array( 2543 'course' => $course->id, 2544 'name' => 'First quiz', 2545 'grade' => 90.00 2546 ); 2547 $options = array( 2548 'idnumber' => 'ABC', 2549 'visible' => 0 2550 ); 2551 // Hidden activity. 2552 $quiz = self::getDataGenerator()->create_module('quiz', $record, $options); 2553 2554 // Test admin user can see the complete hidden activity. 2555 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2556 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2557 2558 $this->assertCount(0, $result['warnings']); 2559 // Test we retrieve all the fields. 2560 $this->assertCount(28, $result['cm']); 2561 $this->assertEquals($record['name'], $result['cm']['name']); 2562 $this->assertEquals($record['grade'], $result['cm']['grade']); 2563 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2564 $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $result['cm']['downloadcontent']); 2565 2566 $student = $this->getDataGenerator()->create_user(); 2567 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2568 2569 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2570 $this->setUser($student); 2571 2572 // The user shouldn't be able to see the activity. 2573 try { 2574 core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2575 $this->fail('Exception expected due to invalid permissions.'); 2576 } catch (moodle_exception $e) { 2577 $this->assertEquals('requireloginerror', $e->errorcode); 2578 } 2579 2580 // Make module visible. 2581 set_coursemodule_visible($quiz->cmid, 1); 2582 2583 // Test student user. 2584 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2585 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2586 2587 $this->assertCount(0, $result['warnings']); 2588 // Test we retrieve only the few files we can see. 2589 $this->assertCount(12, $result['cm']); 2590 $this->assertEquals($quiz->cmid, $result['cm']['id']); 2591 $this->assertEquals($course->id, $result['cm']['course']); 2592 $this->assertEquals('quiz', $result['cm']['modname']); 2593 $this->assertEquals($quiz->id, $result['cm']['instance']); 2594 2595 // Try with an invalid module name. 2596 try { 2597 core_course_external::get_course_module_by_instance('abc', $quiz->id); 2598 $this->fail('Exception expected due to invalid module name.'); 2599 } catch (dml_read_exception $e) { 2600 $this->assertEquals('dmlreadexception', $e->errorcode); 2601 } 2602 2603 } 2604 2605 /** 2606 * Test get_user_navigation_options 2607 */ 2608 public function test_get_user_navigation_options() { 2609 global $USER; 2610 2611 $this->resetAfterTest(); 2612 $course1 = self::getDataGenerator()->create_course(); 2613 $course2 = self::getDataGenerator()->create_course(); 2614 2615 // Create a viewer user. 2616 $viewer = self::getDataGenerator()->create_user(); 2617 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2618 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2619 2620 $this->setUser($viewer->id); 2621 $courses = array($course1->id , $course2->id, SITEID); 2622 2623 $result = core_course_external::get_user_navigation_options($courses); 2624 $result = external_api::clean_returnvalue(core_course_external::get_user_navigation_options_returns(), $result); 2625 2626 $this->assertCount(0, $result['warnings']); 2627 $this->assertCount(3, $result['courses']); 2628 2629 foreach ($result['courses'] as $course) { 2630 $navoptions = new stdClass; 2631 foreach ($course['options'] as $option) { 2632 $navoptions->{$option['name']} = $option['available']; 2633 } 2634 $this->assertCount(8, $course['options']); 2635 if ($course['id'] == SITEID) { 2636 $this->assertTrue($navoptions->blogs); 2637 $this->assertFalse($navoptions->notes); 2638 $this->assertFalse($navoptions->participants); 2639 $this->assertTrue($navoptions->badges); 2640 $this->assertTrue($navoptions->tags); 2641 $this->assertFalse($navoptions->grades); 2642 $this->assertFalse($navoptions->search); 2643 $this->assertTrue($navoptions->competencies); 2644 } else { 2645 $this->assertTrue($navoptions->blogs); 2646 $this->assertFalse($navoptions->notes); 2647 $this->assertTrue($navoptions->participants); 2648 $this->assertFalse($navoptions->badges); 2649 $this->assertFalse($navoptions->tags); 2650 $this->assertTrue($navoptions->grades); 2651 $this->assertFalse($navoptions->search); 2652 $this->assertTrue($navoptions->competencies); 2653 } 2654 } 2655 } 2656 2657 /** 2658 * Test get_user_administration_options 2659 */ 2660 public function test_get_user_administration_options() { 2661 global $USER; 2662 2663 $this->resetAfterTest(); 2664 $course1 = self::getDataGenerator()->create_course(); 2665 $course2 = self::getDataGenerator()->create_course(); 2666 2667 // Create a viewer user. 2668 $viewer = self::getDataGenerator()->create_user(); 2669 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2670 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2671 2672 $this->setUser($viewer->id); 2673 $courses = array($course1->id , $course2->id, SITEID); 2674 2675 $result = core_course_external::get_user_administration_options($courses); 2676 $result = external_api::clean_returnvalue(core_course_external::get_user_administration_options_returns(), $result); 2677 2678 $this->assertCount(0, $result['warnings']); 2679 $this->assertCount(3, $result['courses']); 2680 2681 foreach ($result['courses'] as $course) { 2682 $adminoptions = new stdClass; 2683 foreach ($course['options'] as $option) { 2684 $adminoptions->{$option['name']} = $option['available']; 2685 } 2686 if ($course['id'] == SITEID) { 2687 $this->assertCount(17, $course['options']); 2688 $this->assertFalse($adminoptions->update); 2689 $this->assertFalse($adminoptions->filters); 2690 $this->assertFalse($adminoptions->reports); 2691 $this->assertFalse($adminoptions->backup); 2692 $this->assertFalse($adminoptions->restore); 2693 $this->assertFalse($adminoptions->files); 2694 $this->assertFalse(!isset($adminoptions->tags)); 2695 $this->assertFalse($adminoptions->gradebook); 2696 $this->assertFalse($adminoptions->outcomes); 2697 $this->assertFalse($adminoptions->badges); 2698 $this->assertFalse($adminoptions->import); 2699 $this->assertFalse($adminoptions->reset); 2700 $this->assertFalse($adminoptions->roles); 2701 $this->assertFalse($adminoptions->editcompletion); 2702 $this->assertFalse($adminoptions->copy); 2703 } else { 2704 $this->assertCount(15, $course['options']); 2705 $this->assertFalse($adminoptions->update); 2706 $this->assertFalse($adminoptions->filters); 2707 $this->assertFalse($adminoptions->reports); 2708 $this->assertFalse($adminoptions->backup); 2709 $this->assertFalse($adminoptions->restore); 2710 $this->assertFalse($adminoptions->files); 2711 $this->assertFalse($adminoptions->tags); 2712 $this->assertFalse($adminoptions->gradebook); 2713 $this->assertFalse($adminoptions->outcomes); 2714 $this->assertTrue($adminoptions->badges); 2715 $this->assertFalse($adminoptions->import); 2716 $this->assertFalse($adminoptions->reset); 2717 $this->assertFalse($adminoptions->roles); 2718 $this->assertFalse($adminoptions->editcompletion); 2719 $this->assertFalse($adminoptions->copy); 2720 } 2721 } 2722 } 2723 2724 /** 2725 * Test get_courses_by_fields 2726 */ 2727 public function test_get_courses_by_field() { 2728 global $DB; 2729 $this->resetAfterTest(true); 2730 2731 $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1')); 2732 $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id)); 2733 $course1 = self::getDataGenerator()->create_course( 2734 array('category' => $category1->id, 'shortname' => 'c1', 'format' => 'topics')); 2735 2736 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 2737 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 2738 'categoryid' => $fieldcategory->get('id')]; 2739 $field = self::getDataGenerator()->create_custom_field($customfield); 2740 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; 2741 $course2 = self::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id, 'idnumber' => 'i2', 'customfields' => [$customfieldvalue])); 2742 2743 $student1 = self::getDataGenerator()->create_user(); 2744 $user1 = self::getDataGenerator()->create_user(); 2745 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2746 self::getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id); 2747 self::getDataGenerator()->enrol_user($student1->id, $course2->id, $studentrole->id); 2748 2749 self::setAdminUser(); 2750 // As admins, we should be able to retrieve everything. 2751 $result = core_course_external::get_courses_by_field(); 2752 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2753 $this->assertCount(3, $result['courses']); 2754 // Expect to receive all the fields. 2755 $this->assertCount(40, $result['courses'][0]); 2756 $this->assertCount(41, $result['courses'][1]); // One more field because is not the site course. 2757 $this->assertCount(41, $result['courses'][2]); // One more field because is not the site course. 2758 2759 $result = core_course_external::get_courses_by_field('id', $course1->id); 2760 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2761 $this->assertCount(1, $result['courses']); 2762 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2763 // Expect to receive all the fields. 2764 $this->assertCount(41, $result['courses'][0]); 2765 // Check default values for course format topics. 2766 $this->assertCount(3, $result['courses'][0]['courseformatoptions']); 2767 foreach ($result['courses'][0]['courseformatoptions'] as $option) { 2768 switch ($option['name']) { 2769 case 'hiddensections': 2770 $this->assertEquals(1, $option['value']); 2771 break; 2772 case 'coursedisplay': 2773 $this->assertEquals(0, $option['value']); 2774 break; 2775 case 'indentation': 2776 $this->assertEquals(1, $option['value']); 2777 break; 2778 default: 2779 } 2780 } 2781 2782 $result = core_course_external::get_courses_by_field('id', $course2->id); 2783 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2784 $this->assertCount(1, $result['courses']); 2785 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2786 // Check custom fields properly returned. 2787 $this->assertEquals([ 2788 'shortname' => $customfield['shortname'], 2789 'name' => $customfield['name'], 2790 'type' => $customfield['type'], 2791 'value' => $customfieldvalue['value'], 2792 'valueraw' => $customfieldvalue['value'], 2793 ], $result['courses'][0]['customfields'][0]); 2794 2795 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2796 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2797 $this->assertCount(2, $result['courses']); 2798 2799 // Check default filters. 2800 $this->assertCount(6, $result['courses'][0]['filters']); 2801 $this->assertCount(6, $result['courses'][1]['filters']); 2802 2803 $result = core_course_external::get_courses_by_field('category', $category1->id); 2804 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2805 $this->assertCount(1, $result['courses']); 2806 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2807 $this->assertEquals('Cat 1', $result['courses'][0]['categoryname']); 2808 2809 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2810 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2811 $this->assertCount(1, $result['courses']); 2812 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2813 2814 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2815 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2816 $this->assertCount(1, $result['courses']); 2817 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2818 2819 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2820 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2821 $this->assertCount(0, $result['courses']); 2822 2823 // Change filter value. 2824 filter_set_local_state('mediaplugin', context_course::instance($course1->id)->id, TEXTFILTER_OFF); 2825 2826 self::setUser($student1); 2827 // All visible courses (including front page) for normal student. 2828 $result = core_course_external::get_courses_by_field(); 2829 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2830 $this->assertCount(2, $result['courses']); 2831 $this->assertCount(33, $result['courses'][0]); 2832 $this->assertCount(34, $result['courses'][1]); // One field more (course format options), not present in site course. 2833 2834 $result = core_course_external::get_courses_by_field('id', $course1->id); 2835 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2836 $this->assertCount(1, $result['courses']); 2837 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2838 // Expect to receive all the files that a student can see. 2839 $this->assertCount(34, $result['courses'][0]); 2840 2841 // Check default filters. 2842 $filters = $result['courses'][0]['filters']; 2843 $this->assertCount(6, $filters); 2844 $found = false; 2845 foreach ($filters as $filter) { 2846 if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF) { 2847 $found = true; 2848 } 2849 } 2850 $this->assertTrue($found); 2851 2852 // Course 2 is not visible. 2853 $result = core_course_external::get_courses_by_field('id', $course2->id); 2854 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2855 $this->assertCount(0, $result['courses']); 2856 2857 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2858 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2859 $this->assertCount(1, $result['courses']); 2860 2861 $result = core_course_external::get_courses_by_field('category', $category1->id); 2862 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2863 $this->assertCount(1, $result['courses']); 2864 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2865 2866 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2867 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2868 $this->assertCount(1, $result['courses']); 2869 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2870 2871 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2872 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2873 $this->assertCount(0, $result['courses']); 2874 2875 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2876 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2877 $this->assertCount(0, $result['courses']); 2878 2879 self::setUser($user1); 2880 // All visible courses (including front page) for authenticated user. 2881 $result = core_course_external::get_courses_by_field(); 2882 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2883 $this->assertCount(2, $result['courses']); 2884 $this->assertCount(33, $result['courses'][0]); // Site course. 2885 $this->assertCount(16, $result['courses'][1]); // Only public information, not enrolled. 2886 2887 $result = core_course_external::get_courses_by_field('id', $course1->id); 2888 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2889 $this->assertCount(1, $result['courses']); 2890 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2891 // Expect to receive all the files that a authenticated can see. 2892 $this->assertCount(16, $result['courses'][0]); 2893 2894 // Course 2 is not visible. 2895 $result = core_course_external::get_courses_by_field('id', $course2->id); 2896 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2897 $this->assertCount(0, $result['courses']); 2898 2899 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2900 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2901 $this->assertCount(1, $result['courses']); 2902 2903 $result = core_course_external::get_courses_by_field('category', $category1->id); 2904 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2905 $this->assertCount(1, $result['courses']); 2906 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2907 2908 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2909 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2910 $this->assertCount(1, $result['courses']); 2911 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2912 2913 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2914 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2915 $this->assertCount(0, $result['courses']); 2916 2917 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2918 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2919 $this->assertCount(0, $result['courses']); 2920 } 2921 2922 /** 2923 * Test retrieving courses by field returns custom field data 2924 */ 2925 public function test_get_courses_by_field_customfields(): void { 2926 $this->resetAfterTest(); 2927 $this->setAdminUser(); 2928 2929 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 2930 $datefield = $this->getDataGenerator()->create_custom_field([ 2931 'categoryid' => $fieldcategory->get('id'), 2932 'shortname' => 'mydate', 2933 'name' => 'My date', 2934 'type' => 'date', 2935 ]); 2936 2937 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 2938 [ 2939 'shortname' => $datefield->get('shortname'), 2940 'value' => 1580389200, // 30/01/2020 13:00 GMT. 2941 ], 2942 ]]); 2943 2944 $result = external_api::clean_returnvalue( 2945 core_course_external::get_courses_by_field_returns(), 2946 core_course_external::get_courses_by_field('id', $newcourse->id) 2947 ); 2948 2949 $this->assertCount(1, $result['courses']); 2950 $course = reset($result['courses']); 2951 2952 $this->assertArrayHasKey('customfields', $course); 2953 $this->assertCount(1, $course['customfields']); 2954 2955 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 2956 $this->assertEquals([ 2957 'name' => $datefield->get('name'), 2958 'shortname' => $datefield->get('shortname'), 2959 'type' => $datefield->get('type'), 2960 'value' => userdate(1580389200), 2961 'valueraw' => 1580389200, 2962 ], reset($course['customfields'])); 2963 } 2964 2965 public function test_get_courses_by_field_invalid_field() { 2966 $this->expectException('invalid_parameter_exception'); 2967 $result = core_course_external::get_courses_by_field('zyx', 'x'); 2968 } 2969 2970 public function test_get_courses_by_field_invalid_courses() { 2971 $result = core_course_external::get_courses_by_field('id', '-1'); 2972 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2973 $this->assertCount(0, $result['courses']); 2974 } 2975 2976 /** 2977 * Test get_courses_by_field_invalid_theme_and_lang 2978 */ 2979 public function test_get_courses_by_field_invalid_theme_and_lang() { 2980 $this->resetAfterTest(true); 2981 $this->setAdminUser(); 2982 2983 $course = self::getDataGenerator()->create_course(array('theme' => 'kkt', 'lang' => 'kkl')); 2984 $result = core_course_external::get_courses_by_field('id', $course->id); 2985 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2986 $this->assertEmpty($result['courses']['0']['theme']); 2987 $this->assertEmpty($result['courses']['0']['lang']); 2988 } 2989 2990 2991 public function test_check_updates() { 2992 global $DB; 2993 $this->resetAfterTest(true); 2994 $this->setAdminUser(); 2995 2996 // Create different types of activities. 2997 $course = self::getDataGenerator()->create_course(); 2998 $tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz', 2999 'resource', 'scorm', 'survey', 'url', 'wiki'); 3000 3001 $modules = array(); 3002 foreach ($tocreate as $modname) { 3003 $modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id)); 3004 $modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid); 3005 $modules[$modname]['context'] = context_module::instance($modules[$modname]['instance']->cmid); 3006 } 3007 3008 $student = self::getDataGenerator()->create_user(); 3009 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 3010 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 3011 $this->setUser($student); 3012 3013 $since = time(); 3014 $this->waitForSecond(); 3015 $params = array(); 3016 foreach ($modules as $modname => $data) { 3017 $params[$data['cm']->id] = array( 3018 'contextlevel' => 'module', 3019 'id' => $data['cm']->id, 3020 'since' => $since 3021 ); 3022 } 3023 3024 // Check there is nothing updated because modules are fresh new. 3025 $result = core_course_external::check_updates($course->id, $params); 3026 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 3027 $this->assertCount(0, $result['instances']); 3028 $this->assertCount(0, $result['warnings']); 3029 3030 // Test with get_updates_since the same data. 3031 $result = core_course_external::get_updates_since($course->id, $since); 3032 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 3033 $this->assertCount(0, $result['instances']); 3034 $this->assertCount(0, $result['warnings']); 3035 3036 // Update a module after a second. 3037 $this->waitForSecond(); 3038 set_coursemodule_name($modules['forum']['cm']->id, 'New forum name'); 3039 3040 $found = false; 3041 $result = core_course_external::check_updates($course->id, $params); 3042 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 3043 $this->assertCount(1, $result['instances']); 3044 $this->assertCount(0, $result['warnings']); 3045 foreach ($result['instances'] as $module) { 3046 foreach ($module['updates'] as $update) { 3047 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 3048 $found = true; 3049 } 3050 } 3051 } 3052 $this->assertTrue($found); 3053 3054 // Test with get_updates_since the same data. 3055 $result = core_course_external::get_updates_since($course->id, $since); 3056 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 3057 $this->assertCount(1, $result['instances']); 3058 $this->assertCount(0, $result['warnings']); 3059 $found = false; 3060 $this->assertCount(1, $result['instances']); 3061 $this->assertCount(0, $result['warnings']); 3062 foreach ($result['instances'] as $module) { 3063 foreach ($module['updates'] as $update) { 3064 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 3065 $found = true; 3066 } 3067 } 3068 } 3069 $this->assertTrue($found); 3070 3071 // Do not retrieve the configuration field. 3072 $filter = array('files'); 3073 $found = false; 3074 $result = core_course_external::check_updates($course->id, $params, $filter); 3075 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 3076 $this->assertCount(0, $result['instances']); 3077 $this->assertCount(0, $result['warnings']); 3078 $this->assertFalse($found); 3079 3080 // Add invalid cmid. 3081 $params[] = array( 3082 'contextlevel' => 'module', 3083 'id' => -2, 3084 'since' => $since 3085 ); 3086 $result = core_course_external::check_updates($course->id, $params); 3087 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 3088 $this->assertCount(1, $result['warnings']); 3089 $this->assertEquals(-2, $result['warnings'][0]['itemid']); 3090 } 3091 3092 /** 3093 * Test cases for the get_enrolled_courses_by_timeline_classification test. 3094 */ 3095 public function get_get_enrolled_courses_by_timeline_classification_test_cases():array { 3096 $now = time(); 3097 $day = 86400; 3098 3099 $coursedata = [ 3100 [ 3101 'shortname' => 'apast', 3102 'startdate' => $now - ($day * 2), 3103 'enddate' => $now - $day 3104 ], 3105 [ 3106 'shortname' => 'bpast', 3107 'startdate' => $now - ($day * 2), 3108 'enddate' => $now - $day 3109 ], 3110 [ 3111 'shortname' => 'cpast', 3112 'startdate' => $now - ($day * 2), 3113 'enddate' => $now - $day 3114 ], 3115 [ 3116 'shortname' => 'dpast', 3117 'startdate' => $now - ($day * 2), 3118 'enddate' => $now - $day 3119 ], 3120 [ 3121 'shortname' => 'epast', 3122 'startdate' => $now - ($day * 2), 3123 'enddate' => $now - $day 3124 ], 3125 [ 3126 'shortname' => 'ainprogress', 3127 'startdate' => $now - $day, 3128 'enddate' => $now + $day 3129 ], 3130 [ 3131 'shortname' => 'binprogress', 3132 'startdate' => $now - $day, 3133 'enddate' => $now + $day 3134 ], 3135 [ 3136 'shortname' => 'cinprogress', 3137 'startdate' => $now - $day, 3138 'enddate' => $now + $day 3139 ], 3140 [ 3141 'shortname' => 'dinprogress', 3142 'startdate' => $now - $day, 3143 'enddate' => $now + $day 3144 ], 3145 [ 3146 'shortname' => 'einprogress', 3147 'startdate' => $now - $day, 3148 'enddate' => $now + $day 3149 ], 3150 [ 3151 'shortname' => 'afuture', 3152 'startdate' => $now + $day 3153 ], 3154 [ 3155 'shortname' => 'bfuture', 3156 'startdate' => $now + $day 3157 ], 3158 [ 3159 'shortname' => 'cfuture', 3160 'startdate' => $now + $day 3161 ], 3162 [ 3163 'shortname' => 'dfuture', 3164 'startdate' => $now + $day 3165 ], 3166 [ 3167 'shortname' => 'efuture', 3168 'startdate' => $now + $day 3169 ] 3170 ]; 3171 3172 // Raw enrolled courses result set should be returned in this order: 3173 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast, 3174 // dfuture, dinprogress, dpast, efuture, einprogress, epast 3175 // 3176 // By classification the offset values for each record should be: 3177 // COURSE_TIMELINE_FUTURE 3178 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture) 3179 // COURSE_TIMELINE_INPROGRESS 3180 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress) 3181 // COURSE_TIMELINE_PAST 3182 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast). 3183 // 3184 // NOTE: The offset applies to the unfiltered full set of courses before the classification 3185 // filtering is done. 3186 // E.g. In our example if an offset of 2 is given then it would mean the first 3187 // two courses (afuture, ainprogress) are ignored. 3188 return [ 3189 'empty set' => [ 3190 'coursedata' => [], 3191 'classification' => 'future', 3192 'limit' => 2, 3193 'offset' => 0, 3194 'sort' => 'shortname ASC', 3195 'expectedcourses' => [], 3196 'expectednextoffset' => 0, 3197 ], 3198 // COURSE_TIMELINE_FUTURE. 3199 'future not limit no offset' => [ 3200 'coursedata' => $coursedata, 3201 'classification' => 'future', 3202 'limit' => 0, 3203 'offset' => 0, 3204 'sort' => 'shortname ASC', 3205 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3206 'expectednextoffset' => 15, 3207 ], 3208 'future no offset' => [ 3209 'coursedata' => $coursedata, 3210 'classification' => 'future', 3211 'limit' => 2, 3212 'offset' => 0, 3213 'sort' => 'shortname ASC', 3214 'expectedcourses' => ['afuture', 'bfuture'], 3215 'expectednextoffset' => 4, 3216 ], 3217 'future offset' => [ 3218 'coursedata' => $coursedata, 3219 'classification' => 'future', 3220 'limit' => 2, 3221 'offset' => 2, 3222 'sort' => 'shortname ASC', 3223 'expectedcourses' => ['bfuture', 'cfuture'], 3224 'expectednextoffset' => 7, 3225 ], 3226 'future exact limit' => [ 3227 'coursedata' => $coursedata, 3228 'classification' => 'future', 3229 'limit' => 5, 3230 'offset' => 0, 3231 'sort' => 'shortname ASC', 3232 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3233 'expectednextoffset' => 13, 3234 ], 3235 'future limit less results' => [ 3236 'coursedata' => $coursedata, 3237 'classification' => 'future', 3238 'limit' => 10, 3239 'offset' => 0, 3240 'sort' => 'shortname ASC', 3241 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3242 'expectednextoffset' => 15, 3243 ], 3244 'future limit less results with offset' => [ 3245 'coursedata' => $coursedata, 3246 'classification' => 'future', 3247 'limit' => 10, 3248 'offset' => 5, 3249 'sort' => 'shortname ASC', 3250 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'], 3251 'expectednextoffset' => 15, 3252 ], 3253 'all no limit or offset' => [ 3254 'coursedata' => $coursedata, 3255 'classification' => 'all', 3256 'limit' => 0, 3257 'offset' => 0, 3258 'sort' => 'shortname ASC', 3259 'expectedcourses' => [ 3260 'afuture', 3261 'ainprogress', 3262 'apast', 3263 'bfuture', 3264 'binprogress', 3265 'bpast', 3266 'cfuture', 3267 'cinprogress', 3268 'cpast', 3269 'dfuture', 3270 'dinprogress', 3271 'dpast', 3272 'efuture', 3273 'einprogress', 3274 'epast' 3275 ], 3276 'expectednextoffset' => 15, 3277 ], 3278 'all limit no offset' => [ 3279 'coursedata' => $coursedata, 3280 'classification' => 'all', 3281 'limit' => 5, 3282 'offset' => 0, 3283 'sort' => 'shortname ASC', 3284 'expectedcourses' => [ 3285 'afuture', 3286 'ainprogress', 3287 'apast', 3288 'bfuture', 3289 'binprogress' 3290 ], 3291 'expectednextoffset' => 5, 3292 ], 3293 'all limit and offset' => [ 3294 'coursedata' => $coursedata, 3295 'classification' => 'all', 3296 'limit' => 5, 3297 'offset' => 5, 3298 'sort' => 'shortname ASC', 3299 'expectedcourses' => [ 3300 'bpast', 3301 'cfuture', 3302 'cinprogress', 3303 'cpast', 3304 'dfuture' 3305 ], 3306 'expectednextoffset' => 10, 3307 ], 3308 'all offset past result set' => [ 3309 'coursedata' => $coursedata, 3310 'classification' => 'all', 3311 'limit' => 5, 3312 'offset' => 50, 3313 'sort' => 'shortname ASC', 3314 'expectedcourses' => [], 3315 'expectednextoffset' => 50, 3316 ], 3317 'all limit and offset with sort ul.timeaccess desc' => [ 3318 'coursedata' => $coursedata, 3319 'classification' => 'inprogress', 3320 'limit' => 0, 3321 'offset' => 0, 3322 'sort' => 'ul.timeaccess desc', 3323 'expectedcourses' => [ 3324 'ainprogress', 3325 'binprogress', 3326 'cinprogress', 3327 'dinprogress', 3328 'einprogress' 3329 ], 3330 'expectednextoffset' => 15, 3331 ], 3332 'all limit and offset with sort sql injection for sort or 1==1' => [ 3333 'coursedata' => $coursedata, 3334 'classification' => 'all', 3335 'limit' => 5, 3336 'offset' => 5, 3337 'sort' => 'ul.timeaccess desc or 1==1', 3338 'expectedcourses' => [], 3339 'expectednextoffset' => 0, 3340 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3341 ], 3342 'all limit and offset with sql injection of sort a custom one' => [ 3343 'coursedata' => $coursedata, 3344 'classification' => 'all', 3345 'limit' => 5, 3346 'offset' => 5, 3347 'sort' => "ul.timeaccess LIMIT 1--", 3348 'expectedcourses' => [], 3349 'expectednextoffset' => 0, 3350 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3351 ], 3352 'all limit and offset with wrong sort direction' => [ 3353 'coursedata' => $coursedata, 3354 'classification' => 'all', 3355 'limit' => 5, 3356 'offset' => 5, 3357 'sort' => "ul.timeaccess abcdasc", 3358 'expectedcourses' => [], 3359 'expectednextoffset' => 0, 3360 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()', 3361 ], 3362 'all limit and offset with wrong sort direction' => [ 3363 'coursedata' => $coursedata, 3364 'classification' => 'all', 3365 'limit' => 5, 3366 'offset' => 5, 3367 'sort' => "ul.timeaccess.foo ascd", 3368 'expectedcourses' => [], 3369 'expectednextoffset' => 0, 3370 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()', 3371 ], 3372 'all limit and offset with wrong sort param' => [ 3373 'coursedata' => $coursedata, 3374 'classification' => 'all', 3375 'limit' => 5, 3376 'offset' => 5, 3377 'sort' => "foobar", 3378 'expectedcourses' => [], 3379 'expectednextoffset' => 0, 3380 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3381 ], 3382 'all limit and offset with wrong field name' => [ 3383 'coursedata' => $coursedata, 3384 'classification' => 'all', 3385 'limit' => 5, 3386 'offset' => 5, 3387 'sort' => "ul.foobar", 3388 'expectedcourses' => [], 3389 'expectednextoffset' => 0, 3390 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3391 ], 3392 'all limit and offset with wrong field separator' => [ 3393 'coursedata' => $coursedata, 3394 'classification' => 'all', 3395 'limit' => 5, 3396 'offset' => 5, 3397 'sort' => "ul.timeaccess.foo", 3398 'expectedcourses' => [], 3399 'expectednextoffset' => 0, 3400 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3401 ], 3402 'all limit and offset with wrong field separator #' => [ 3403 'coursedata' => $coursedata, 3404 'classification' => 'all', 3405 'limit' => 5, 3406 'offset' => 5, 3407 'sort' => "ul#timeaccess", 3408 'expectedcourses' => [], 3409 'expectednextoffset' => 0, 3410 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3411 ], 3412 'all limit and offset with wrong field separator $' => [ 3413 'coursedata' => $coursedata, 3414 'classification' => 'all', 3415 'limit' => 5, 3416 'offset' => 5, 3417 'sort' => 'ul$timeaccess', 3418 'expectedcourses' => [], 3419 'expectednextoffset' => 0, 3420 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3421 ], 3422 'all limit and offset with wrong field name' => [ 3423 'coursedata' => $coursedata, 3424 'classification' => 'all', 3425 'limit' => 5, 3426 'offset' => 5, 3427 'sort' => 'timeaccess123', 3428 'expectedcourses' => [], 3429 'expectednextoffset' => 0, 3430 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()', 3431 ], 3432 'all limit and offset with no sort direction for ul' => [ 3433 'coursedata' => $coursedata, 3434 'classification' => 'inprogress', 3435 'limit' => 0, 3436 'offset' => 0, 3437 'sort' => "ul.timeaccess", 3438 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3439 'expectednextoffset' => 15, 3440 ], 3441 'all limit and offset with valid field name and no prefix, test for ul' => [ 3442 'coursedata' => $coursedata, 3443 'classification' => 'inprogress', 3444 'limit' => 0, 3445 'offset' => 0, 3446 'sort' => "timeaccess", 3447 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3448 'expectednextoffset' => 15, 3449 ], 3450 'all limit and offset with valid field name and no prefix' => [ 3451 'coursedata' => $coursedata, 3452 'classification' => 'all', 3453 'limit' => 5, 3454 'offset' => 5, 3455 'sort' => "fullname", 3456 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3457 'expectednextoffset' => 10, 3458 ], 3459 'all limit and offset with valid field name and no prefix and with sort direction' => [ 3460 'coursedata' => $coursedata, 3461 'classification' => 'all', 3462 'limit' => 5, 3463 'offset' => 5, 3464 'sort' => "fullname desc", 3465 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3466 'expectednextoffset' => 10, 3467 ], 3468 'Search courses for courses containing bfut' => [ 3469 'coursedata' => $coursedata, 3470 'classification' => 'search', 3471 'limit' => 0, 3472 'offset' => 0, 3473 'sort' => null, 3474 'expectedcourses' => ['bfuture'], 3475 'expectednextoffset' => 1, 3476 'expectedexception' => null, 3477 'searchvalue' => 'bfut', 3478 ], 3479 'Search courses for courses containing inp' => [ 3480 'coursedata' => $coursedata, 3481 'classification' => 'search', 3482 'limit' => 0, 3483 'offset' => 0, 3484 'sort' => null, 3485 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3486 'expectednextoffset' => 5, 3487 'expectedexception' => null, 3488 'searchvalue' => 'inp', 3489 ], 3490 'Search courses for courses containing fail' => [ 3491 'coursedata' => $coursedata, 3492 'classification' => 'search', 3493 'limit' => 0, 3494 'offset' => 0, 3495 'sort' => null, 3496 'expectedcourses' => [], 3497 'expectednextoffset' => 0, 3498 'expectedexception' => null, 3499 'searchvalue' => 'fail', 3500 ], 3501 'Search courses for courses containing !`~[]C' => [ 3502 'coursedata' => $coursedata, 3503 'classification' => 'search', 3504 'limit' => 0, 3505 'offset' => 0, 3506 'sort' => null, 3507 'expectedcourses' => [], 3508 'expectednextoffset' => 0, 3509 'expectedexception' => null, 3510 'searchvalue' => '!`~[]C', 3511 ], 3512 ]; 3513 } 3514 3515 /** 3516 * Test the get_enrolled_courses_by_timeline_classification function. 3517 * 3518 * @dataProvider get_get_enrolled_courses_by_timeline_classification_test_cases() 3519 * @param array $coursedata Courses to create 3520 * @param string $classification Timeline classification 3521 * @param int $limit Maximum number of results 3522 * @param int $offset Offset the unfiltered courses result set by this amount 3523 * @param string $sort sort the courses 3524 * @param array $expectedcourses Expected courses in result 3525 * @param int $expectednextoffset Expected next offset value in result 3526 * @param string|null $expectedexception Expected exception string 3527 * @param string|null $searchvalue If we are searching, what do we need to look for? 3528 */ 3529 public function test_get_enrolled_courses_by_timeline_classification( 3530 $coursedata, 3531 $classification, 3532 $limit, 3533 $offset, 3534 $sort, 3535 $expectedcourses, 3536 $expectednextoffset, 3537 $expectedexception = null, 3538 $searchvalue = null 3539 ) { 3540 $this->resetAfterTest(); 3541 $generator = $this->getDataGenerator(); 3542 3543 $courses = array_map(function($coursedata) use ($generator) { 3544 return $generator->create_course($coursedata); 3545 }, $coursedata); 3546 3547 $student = $generator->create_user(); 3548 3549 foreach ($courses as $course) { 3550 $generator->enrol_user($student->id, $course->id, 'student'); 3551 } 3552 3553 $this->setUser($student); 3554 3555 if (isset($expectedexception)) { 3556 $this->expectException('coding_exception'); 3557 $this->expectExceptionMessage($expectedexception); 3558 } 3559 3560 // NOTE: The offset applies to the unfiltered full set of courses before the classification 3561 // filtering is done. 3562 // E.g. In our example if an offset of 2 is given then it would mean the first 3563 // two courses (afuture, ainprogress) are ignored. 3564 $result = core_course_external::get_enrolled_courses_by_timeline_classification( 3565 $classification, 3566 $limit, 3567 $offset, 3568 $sort, 3569 null, 3570 null, 3571 $searchvalue 3572 ); 3573 $result = external_api::clean_returnvalue( 3574 core_course_external::get_enrolled_courses_by_timeline_classification_returns(), 3575 $result 3576 ); 3577 3578 $actual = array_map(function($course) { 3579 return $course['shortname']; 3580 }, $result['courses']); 3581 3582 $this->assertEqualsCanonicalizing($expectedcourses, $actual); 3583 $this->assertEquals($expectednextoffset, $result['nextoffset']); 3584 } 3585 3586 /** 3587 * Test the get_recent_courses function. 3588 */ 3589 public function test_get_recent_courses() { 3590 global $USER, $DB; 3591 3592 $this->resetAfterTest(); 3593 $generator = $this->getDataGenerator(); 3594 3595 set_config('hiddenuserfields', 'lastaccess'); 3596 3597 $courses = array(); 3598 for ($i = 1; $i < 12; $i++) { 3599 $courses[] = $generator->create_course(); 3600 }; 3601 3602 $student = $generator->create_user(); 3603 $teacher = $generator->create_user(); 3604 3605 foreach ($courses as $course) { 3606 $generator->enrol_user($student->id, $course->id, 'student'); 3607 } 3608 3609 $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher'); 3610 3611 $this->setUser($student); 3612 3613 $result = core_course_external::get_recent_courses($USER->id); 3614 3615 // No course accessed. 3616 $this->assertCount(0, $result); 3617 3618 foreach ($courses as $course) { 3619 core_course_external::view_course($course->id); 3620 } 3621 3622 // Every course accessed. 3623 $result = core_course_external::get_recent_courses($USER->id); 3624 $this->assertCount( 11, $result); 3625 3626 // Every course accessed, result limited to 10 courses. 3627 $result = core_course_external::get_recent_courses($USER->id, 10); 3628 $this->assertCount(10, $result); 3629 3630 $guestcourse = $generator->create_course( 3631 (object)array('shortname' => 'guestcourse', 3632 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, 3633 'enrol_guest_password_0' => '')); 3634 core_course_external::view_course($guestcourse->id); 3635 3636 // Every course accessed, even the not enrolled one. 3637 $result = core_course_external::get_recent_courses($USER->id); 3638 $this->assertCount(12, $result); 3639 3640 // Offset 5, return 7 out of 12. 3641 $result = core_course_external::get_recent_courses($USER->id, 0, 5); 3642 $this->assertCount(7, $result); 3643 3644 // Offset 5 and limit 3, return 3 out of 12. 3645 $result = core_course_external::get_recent_courses($USER->id, 3, 5); 3646 $this->assertCount(3, $result); 3647 3648 // Sorted by course id ASC. 3649 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id ASC'); 3650 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3651 3652 // Sorted by course id DESC. 3653 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id DESC'); 3654 $this->assertEquals($guestcourse->id, array_shift($result)->id); 3655 3656 // If last access is hidden, only get the courses where has viewhiddenuserfields capability. 3657 $this->setUser($teacher); 3658 $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 3659 $usercontext = context_user::instance($student->id); 3660 $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid); 3661 3662 // Sorted by course id DESC. 3663 $result = core_course_external::get_recent_courses($student->id); 3664 $this->assertCount(1, $result); 3665 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3666 } 3667 3668 /** 3669 * Test get enrolled users by cmid function. 3670 */ 3671 public function test_get_enrolled_users_by_cmid() { 3672 global $PAGE; 3673 $this->resetAfterTest(true); 3674 3675 $user1 = self::getDataGenerator()->create_user(); 3676 $user2 = self::getDataGenerator()->create_user(); 3677 $user3 = self::getDataGenerator()->create_user(); 3678 3679 $user1picture = new user_picture($user1); 3680 $user1picture->size = 1; 3681 $user1->profileimage = $user1picture->get_url($PAGE)->out(false); 3682 3683 $user2picture = new user_picture($user2); 3684 $user2picture->size = 1; 3685 $user2->profileimage = $user2picture->get_url($PAGE)->out(false); 3686 3687 $user3picture = new user_picture($user3); 3688 $user3picture->size = 1; 3689 $user3->profileimage = $user3picture->get_url($PAGE)->out(false); 3690 3691 // Set the first created user to the test user. 3692 self::setUser($user1); 3693 3694 // Create course to add the module. 3695 $course1 = self::getDataGenerator()->create_course(); 3696 3697 // Forum with tracking off. 3698 $record = new stdClass(); 3699 $record->course = $course1->id; 3700 $forum1 = self::getDataGenerator()->create_module('forum', $record); 3701 3702 // Following lines enrol and assign default role id to the users. 3703 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 3704 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 3705 // Enrol a suspended user in the course. 3706 $this->getDataGenerator()->enrol_user($user3->id, $course1->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 3707 3708 // Create what we expect to be returned when querying the course module. 3709 $expectedusers = array( 3710 'users' => array(), 3711 'warnings' => array(), 3712 ); 3713 3714 $expectedusers['users'][0] = [ 3715 'id' => $user1->id, 3716 'fullname' => fullname($user1), 3717 'firstname' => $user1->firstname, 3718 'lastname' => $user1->lastname, 3719 'profileimage' => $user1->profileimage, 3720 ]; 3721 $expectedusers['users'][1] = [ 3722 'id' => $user2->id, 3723 'fullname' => fullname($user2), 3724 'firstname' => $user2->firstname, 3725 'lastname' => $user2->lastname, 3726 'profileimage' => $user2->profileimage, 3727 ]; 3728 $expectedusers['users'][2] = [ 3729 'id' => $user3->id, 3730 'fullname' => fullname($user3), 3731 'firstname' => $user3->firstname, 3732 'lastname' => $user3->lastname, 3733 'profileimage' => $user3->profileimage, 3734 ]; 3735 3736 // Test getting the users in a given context. 3737 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid); 3738 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users); 3739 3740 $this->assertEquals(3, count($users['users'])); 3741 $this->assertEquals($expectedusers, $users); 3742 3743 // Test getting only the active users in a given context. 3744 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid, 0, true); 3745 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users); 3746 3747 $expectedusers['users'] = [ 3748 [ 3749 'id' => $user1->id, 3750 'fullname' => fullname($user1), 3751 'firstname' => $user1->firstname, 3752 'lastname' => $user1->lastname, 3753 'profileimage' => $user1->profileimage, 3754 ], 3755 [ 3756 'id' => $user2->id, 3757 'fullname' => fullname($user2), 3758 'firstname' => $user2->firstname, 3759 'lastname' => $user2->lastname, 3760 'profileimage' => $user2->profileimage, 3761 ] 3762 ]; 3763 3764 $this->assertEquals(2, count($users['users'])); 3765 $this->assertEquals($expectedusers, $users); 3766 } 3767 3768 /** 3769 * Verify that content items can be added to user favourites. 3770 */ 3771 public function test_add_content_item_to_user_favourites() { 3772 $this->resetAfterTest(); 3773 3774 $course = $this->getDataGenerator()->create_course(); 3775 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3776 $this->setUser($user); 3777 3778 // Using the internal API, confirm that no items are set as favourites for the user. 3779 $contentitemservice = new \core_course\local\service\content_item_service( 3780 new \core_course\local\repository\content_item_readonly_repository() 3781 ); 3782 $contentitems = $contentitemservice->get_all_content_items($user); 3783 $favourited = array_filter($contentitems, function($contentitem) { 3784 return $contentitem->favourite == true; 3785 }); 3786 $this->assertCount(0, $favourited); 3787 3788 // Using the external API, favourite a content item for the user. 3789 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3790 $contentitem = core_course_external::add_content_item_to_user_favourites('mod_assign', $assign->id, $user->id); 3791 $contentitem = external_api::clean_returnvalue(core_course_external::add_content_item_to_user_favourites_returns(), 3792 $contentitem); 3793 3794 // Verify the returned item is a favourite. 3795 $this->assertTrue($contentitem['favourite']); 3796 3797 // Using the internal API, confirm we see a single favourite item. 3798 $contentitems = $contentitemservice->get_all_content_items($user); 3799 $favourited = array_values(array_filter($contentitems, function($contentitem) { 3800 return $contentitem->favourite == true; 3801 })); 3802 $this->assertCount(1, $favourited); 3803 $this->assertEquals('assign', $favourited[0]->name); 3804 } 3805 3806 /** 3807 * Verify that content items can be removed from user favourites. 3808 */ 3809 public function test_remove_content_item_from_user_favourites() { 3810 $this->resetAfterTest(); 3811 3812 $course = $this->getDataGenerator()->create_course(); 3813 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3814 $this->setUser($user); 3815 3816 // Using the internal API, set a favourite for the user. 3817 $contentitemservice = new \core_course\local\service\content_item_service( 3818 new \core_course\local\repository\content_item_readonly_repository() 3819 ); 3820 $contentitems = $contentitemservice->get_all_content_items($user); 3821 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3822 $contentitemservice->add_to_user_favourites($user, $assign->componentname, $assign->id); 3823 3824 $contentitems = $contentitemservice->get_all_content_items($user); 3825 $favourited = array_filter($contentitems, function($contentitem) { 3826 return $contentitem->favourite == true; 3827 }); 3828 $this->assertCount(1, $favourited); 3829 3830 // Now, verify the external API can remove the favourite. 3831 $contentitem = core_course_external::remove_content_item_from_user_favourites('mod_assign', $assign->id); 3832 $contentitem = external_api::clean_returnvalue(core_course_external::remove_content_item_from_user_favourites_returns(), 3833 $contentitem); 3834 3835 // Verify the returned item is a favourite. 3836 $this->assertFalse($contentitem['favourite']); 3837 3838 // Using the internal API, confirm we see no favourite items. 3839 $contentitems = $contentitemservice->get_all_content_items($user); 3840 $favourited = array_filter($contentitems, function($contentitem) { 3841 return $contentitem->favourite == true; 3842 }); 3843 $this->assertCount(0, $favourited); 3844 } 3845 3846 /** 3847 * Test the web service returning course content items for inclusion in activity choosers, etc. 3848 */ 3849 public function test_get_course_content_items() { 3850 $this->resetAfterTest(); 3851 3852 $course = self::getDataGenerator()->create_course(); 3853 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3854 3855 // Fetch available content items as the editing teacher. 3856 $this->setUser($user); 3857 $result = core_course_external::get_course_content_items($course->id); 3858 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3859 3860 $contentitemservice = new \core_course\local\service\content_item_service( 3861 new \core_course\local\repository\content_item_readonly_repository() 3862 ); 3863 3864 // Check if the webservice returns exactly what the service defines, albeit in array form. 3865 $serviceitemsasarray = array_map(function($item) { 3866 return (array) $item; 3867 }, $contentitemservice->get_content_items_for_user_in_course($user, $course)); 3868 3869 $this->assertEquals($serviceitemsasarray, $result['content_items']); 3870 } 3871 3872 /** 3873 * Test the web service returning course content items, specifically in case where the user can't manage activities. 3874 */ 3875 public function test_get_course_content_items_no_permission_to_manage() { 3876 $this->resetAfterTest(); 3877 3878 $course = self::getDataGenerator()->create_course(); 3879 $user = self::getDataGenerator()->create_and_enrol($course, 'student'); 3880 3881 // Fetch available content items as a student, who won't have the permission to manage activities. 3882 $this->setUser($user); 3883 $result = core_course_external::get_course_content_items($course->id); 3884 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3885 3886 $this->assertEmpty($result['content_items']); 3887 } 3888 3889 /** 3890 * Test toggling the recommendation of an activity. 3891 */ 3892 public function test_toggle_activity_recommendation() { 3893 global $CFG; 3894 3895 $this->resetAfterTest(); 3896 3897 $context = context_system::instance(); 3898 $usercontext = context_user::instance($CFG->siteguest); 3899 $component = 'core_course'; 3900 $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext); 3901 3902 $areaname = 'test_core'; 3903 $areaid = 3; 3904 3905 // Test we have the favourite. 3906 $this->setAdminUser(); 3907 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3908 $this->assertTrue($favouritefactory->favourite_exists($component, 3909 \core_course\local\service\content_item_service::RECOMMENDATION_PREFIX . $areaname, $areaid, $context)); 3910 $this->assertTrue($result['status']); 3911 // Test that it is now gone. 3912 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3913 $this->assertFalse($favouritefactory->favourite_exists($component, $areaname, $areaid, $context)); 3914 $this->assertFalse($result['status']); 3915 } 3916 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body