Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * 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 core_course_externallib_testcase extends externallib_advanced_testcase { 41 42 /** 43 * Tests set up 44 */ 45 protected function setUp(): void { 46 global $CFG; 47 require_once($CFG->dirroot . '/course/externallib.php'); 48 } 49 50 /** 51 * Test create_categories 52 */ 53 public function test_create_categories() { 54 55 global $DB; 56 57 $this->resetAfterTest(true); 58 59 // Set the required capabilities by the external function 60 $contextid = context_system::instance()->id; 61 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 62 63 // Create base categories. 64 $category1 = new stdClass(); 65 $category1->name = 'Root Test Category 1'; 66 $category2 = new stdClass(); 67 $category2->name = 'Root Test Category 2'; 68 $category2->idnumber = 'rootcattest2'; 69 $category2->desc = 'Description for root test category 1'; 70 $category2->theme = 'classic'; 71 $categories = array( 72 array('name' => $category1->name, 'parent' => 0), 73 array('name' => $category2->name, 'parent' => 0, 'idnumber' => $category2->idnumber, 74 'description' => $category2->desc, 'theme' => $category2->theme) 75 ); 76 77 $createdcats = core_course_external::create_categories($categories); 78 79 // We need to execute the return values cleaning process to simulate the web service server. 80 $createdcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdcats); 81 82 // Initially confirm that base data was inserted correctly. 83 $this->assertEquals($category1->name, $createdcats[0]['name']); 84 $this->assertEquals($category2->name, $createdcats[1]['name']); 85 86 // Save the ids. 87 $category1->id = $createdcats[0]['id']; 88 $category2->id = $createdcats[1]['id']; 89 90 // Create on sub category. 91 $category3 = new stdClass(); 92 $category3->name = 'Sub Root Test Category 3'; 93 $subcategories = array( 94 array('name' => $category3->name, 'parent' => $category1->id) 95 ); 96 97 $createdsubcats = core_course_external::create_categories($subcategories); 98 99 // We need to execute the return values cleaning process to simulate the web service server. 100 $createdsubcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdsubcats); 101 102 // Confirm that sub categories were inserted correctly. 103 $this->assertEquals($category3->name, $createdsubcats[0]['name']); 104 105 // Save the ids. 106 $category3->id = $createdsubcats[0]['id']; 107 108 // Calling the ws function should provide a new sortorder to give category1, 109 // category2, category3. New course categories are ordered by id not name. 110 $category1 = $DB->get_record('course_categories', array('id' => $category1->id)); 111 $category2 = $DB->get_record('course_categories', array('id' => $category2->id)); 112 $category3 = $DB->get_record('course_categories', array('id' => $category3->id)); 113 114 // sortorder sequence (and sortorder) must be: 115 // category 1 116 // category 3 117 // category 2 118 $this->assertGreaterThan($category1->sortorder, $category3->sortorder); 119 $this->assertGreaterThan($category3->sortorder, $category2->sortorder); 120 121 // Call without required capability 122 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 123 $this->expectException('required_capability_exception'); 124 $createdsubcats = core_course_external::create_categories($subcategories); 125 126 } 127 128 /** 129 * Test delete categories 130 */ 131 public function test_delete_categories() { 132 global $DB; 133 134 $this->resetAfterTest(true); 135 136 // Set the required capabilities by the external function 137 $contextid = context_system::instance()->id; 138 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 139 140 $category1 = self::getDataGenerator()->create_category(); 141 $category2 = self::getDataGenerator()->create_category( 142 array('parent' => $category1->id)); 143 $category3 = self::getDataGenerator()->create_category(); 144 $category4 = self::getDataGenerator()->create_category( 145 array('parent' => $category3->id)); 146 $category5 = self::getDataGenerator()->create_category( 147 array('parent' => $category4->id)); 148 149 //delete category 1 and 2 + delete category 4, category 5 moved under category 3 150 core_course_external::delete_categories(array( 151 array('id' => $category1->id, 'recursive' => 1), 152 array('id' => $category4->id) 153 )); 154 155 //check $category 1 and 2 are deleted 156 $notdeletedcount = $DB->count_records_select('course_categories', 157 'id IN ( ' . $category1->id . ',' . $category2->id . ',' . $category4->id . ')'); 158 $this->assertEquals(0, $notdeletedcount); 159 160 //check that $category5 as $category3 for parent 161 $dbcategory5 = $DB->get_record('course_categories', array('id' => $category5->id)); 162 $this->assertEquals($dbcategory5->path, $category3->path . '/' . $category5->id); 163 164 // Call without required capability 165 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 166 $this->expectException('required_capability_exception'); 167 $createdsubcats = core_course_external::delete_categories( 168 array(array('id' => $category3->id))); 169 } 170 171 /** 172 * Test get categories 173 */ 174 public function test_get_categories() { 175 global $DB; 176 177 $this->resetAfterTest(true); 178 179 $generatedcats = array(); 180 $category1data['idnumber'] = 'idnumbercat1'; 181 $category1data['name'] = 'Category 1 for PHPunit test'; 182 $category1data['description'] = 'Category 1 description'; 183 $category1data['descriptionformat'] = FORMAT_MOODLE; 184 $category1 = self::getDataGenerator()->create_category($category1data); 185 $generatedcats[$category1->id] = $category1; 186 $category2 = self::getDataGenerator()->create_category( 187 array('parent' => $category1->id)); 188 $generatedcats[$category2->id] = $category2; 189 $category6 = self::getDataGenerator()->create_category( 190 array('parent' => $category1->id, 'visible' => 0)); 191 $generatedcats[$category6->id] = $category6; 192 $category3 = self::getDataGenerator()->create_category(); 193 $generatedcats[$category3->id] = $category3; 194 $category4 = self::getDataGenerator()->create_category( 195 array('parent' => $category3->id)); 196 $generatedcats[$category4->id] = $category4; 197 $category5 = self::getDataGenerator()->create_category( 198 array('parent' => $category4->id)); 199 $generatedcats[$category5->id] = $category5; 200 201 // Set the required capabilities by the external function. 202 $context = context_system::instance(); 203 $roleid = $this->assignUserCapability('moodle/category:manage', $context->id); 204 $this->assignUserCapability('moodle/category:viewhiddencategories', $context->id, $roleid); 205 206 // Retrieve category1 + sub-categories except not visible ones 207 $categories = core_course_external::get_categories(array( 208 array('key' => 'id', 'value' => $category1->id), 209 array('key' => 'visible', 'value' => 1)), 1); 210 211 // We need to execute the return values cleaning process to simulate the web service server. 212 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 213 214 // Check we retrieve the good total number of categories. 215 $this->assertEquals(2, count($categories)); 216 217 // Check the return values 218 foreach ($categories as $category) { 219 $generatedcat = $generatedcats[$category['id']]; 220 $this->assertEquals($category['idnumber'], $generatedcat->idnumber); 221 $this->assertEquals($category['name'], $generatedcat->name); 222 // Description was converted to the HTML format. 223 $this->assertEquals($category['description'], format_text($generatedcat->description, FORMAT_MOODLE, array('para' => false))); 224 $this->assertEquals($category['descriptionformat'], FORMAT_HTML); 225 } 226 227 // Check categories by ids. 228 $ids = implode(',', array_keys($generatedcats)); 229 $categories = core_course_external::get_categories(array( 230 array('key' => 'ids', 'value' => $ids)), 0); 231 232 // We need to execute the return values cleaning process to simulate the web service server. 233 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 234 235 // Check we retrieve the good total number of categories. 236 $this->assertEquals(6, count($categories)); 237 // Check ids. 238 $returnedids = []; 239 foreach ($categories as $category) { 240 $returnedids[] = $category['id']; 241 } 242 // Sort the arrays upon comparision. 243 $this->assertEqualsCanonicalizing(array_keys($generatedcats), $returnedids); 244 245 // Check different params. 246 $categories = core_course_external::get_categories(array( 247 array('key' => 'id', 'value' => $category1->id), 248 array('key' => 'ids', 'value' => $category1->id), 249 array('key' => 'idnumber', 'value' => $category1->idnumber), 250 array('key' => 'visible', 'value' => 1)), 0); 251 252 // We need to execute the return values cleaning process to simulate the web service server. 253 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 254 255 $this->assertEquals(1, count($categories)); 256 257 // Same query, but forcing a parameters clean. 258 $categories = core_course_external::get_categories(array( 259 array('key' => 'id', 'value' => "$category1->id"), 260 array('key' => 'idnumber', 'value' => $category1->idnumber), 261 array('key' => 'name', 'value' => $category1->name . "<br/>"), 262 array('key' => 'visible', 'value' => '1')), 0); 263 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 264 265 $this->assertEquals(1, count($categories)); 266 267 // Retrieve categories from parent. 268 $categories = core_course_external::get_categories(array( 269 array('key' => 'parent', 'value' => $category3->id)), 1); 270 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 271 272 $this->assertEquals(2, count($categories)); 273 274 // Retrieve all categories. 275 $categories = core_course_external::get_categories(); 276 277 // We need to execute the return values cleaning process to simulate the web service server. 278 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 279 280 $this->assertEquals($DB->count_records('course_categories'), count($categories)); 281 282 $this->unassignUserCapability('moodle/category:viewhiddencategories', $context->id, $roleid); 283 284 // Ensure maxdepthcategory is 2 and retrieve all categories without category:viewhiddencategories capability. 285 // It should retrieve all visible categories as well. 286 set_config('maxcategorydepth', 2); 287 $categories = core_course_external::get_categories(); 288 289 // We need to execute the return values cleaning process to simulate the web service server. 290 $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories); 291 292 $this->assertEquals($DB->count_records('course_categories', array('visible' => 1)), count($categories)); 293 294 // Call without required capability (it will fail cause of the search on idnumber). 295 $this->expectException('moodle_exception'); 296 $categories = core_course_external::get_categories(array( 297 array('key' => 'id', 'value' => $category1->id), 298 array('key' => 'idnumber', 'value' => $category1->idnumber), 299 array('key' => 'visible', 'value' => 1)), 0); 300 } 301 302 /** 303 * Test update_categories 304 */ 305 public function test_update_categories() { 306 global $DB; 307 308 $this->resetAfterTest(true); 309 310 // Set the required capabilities by the external function 311 $contextid = context_system::instance()->id; 312 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid); 313 314 // Create base categories. 315 $category1data['idnumber'] = 'idnumbercat1'; 316 $category1data['name'] = 'Category 1 for PHPunit test'; 317 $category1data['description'] = 'Category 1 description'; 318 $category1data['descriptionformat'] = FORMAT_MOODLE; 319 $category1 = self::getDataGenerator()->create_category($category1data); 320 $category2 = self::getDataGenerator()->create_category( 321 array('parent' => $category1->id)); 322 $category3 = self::getDataGenerator()->create_category(); 323 $category4 = self::getDataGenerator()->create_category( 324 array('parent' => $category3->id)); 325 $category5 = self::getDataGenerator()->create_category( 326 array('parent' => $category4->id)); 327 328 // We update all category1 attribut. 329 // Then we move cat4 and cat5 parent: cat3 => cat1 330 $categories = array( 331 array('id' => $category1->id, 332 'name' => $category1->name . '_updated', 333 'idnumber' => $category1->idnumber . '_updated', 334 'description' => $category1->description . '_updated', 335 'descriptionformat' => FORMAT_HTML, 336 'theme' => $category1->theme), 337 array('id' => $category4->id, 'parent' => $category1->id)); 338 339 core_course_external::update_categories($categories); 340 341 // Check the values were updated. 342 $dbcategories = $DB->get_records_select('course_categories', 343 'id IN (' . $category1->id . ',' . $category2->id . ',' . $category2->id 344 . ',' . $category3->id . ',' . $category4->id . ',' . $category5->id .')'); 345 $this->assertEquals($category1->name . '_updated', 346 $dbcategories[$category1->id]->name); 347 $this->assertEquals($category1->idnumber . '_updated', 348 $dbcategories[$category1->id]->idnumber); 349 $this->assertEquals($category1->description . '_updated', 350 $dbcategories[$category1->id]->description); 351 $this->assertEquals(FORMAT_HTML, $dbcategories[$category1->id]->descriptionformat); 352 353 // Check that category4 and category5 have been properly moved. 354 $this->assertEquals('/' . $category1->id . '/' . $category4->id, 355 $dbcategories[$category4->id]->path); 356 $this->assertEquals('/' . $category1->id . '/' . $category4->id . '/' . $category5->id, 357 $dbcategories[$category5->id]->path); 358 359 // Call without required capability. 360 $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid); 361 $this->expectException('required_capability_exception'); 362 core_course_external::update_categories($categories); 363 } 364 365 /** 366 * Test create_courses numsections 367 */ 368 public function test_create_course_numsections() { 369 global $DB; 370 371 $this->resetAfterTest(true); 372 373 // Set the required capabilities by the external function. 374 $contextid = context_system::instance()->id; 375 $roleid = $this->assignUserCapability('moodle/course:create', $contextid); 376 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 377 378 $numsections = 10; 379 $category = self::getDataGenerator()->create_category(); 380 381 // Create base categories. 382 $course1['fullname'] = 'Test course 1'; 383 $course1['shortname'] = 'Testcourse1'; 384 $course1['categoryid'] = $category->id; 385 $course1['courseformatoptions'][] = array('name' => 'numsections', 'value' => $numsections); 386 387 $courses = array($course1); 388 389 $createdcourses = core_course_external::create_courses($courses); 390 foreach ($createdcourses as $createdcourse) { 391 $existingsections = $DB->get_records('course_sections', array('course' => $createdcourse['id'])); 392 $modinfo = get_fast_modinfo($createdcourse['id']); 393 $sections = $modinfo->get_section_info_all(); 394 $this->assertEquals(count($sections), $numsections + 1); // Includes generic section. 395 $this->assertEquals(count($existingsections), $numsections + 1); // Includes generic section. 396 } 397 } 398 399 /** 400 * Test create_courses 401 */ 402 public function test_create_courses() { 403 global $DB; 404 405 $this->resetAfterTest(true); 406 407 // Enable course completion. 408 set_config('enablecompletion', 1); 409 // Enable course themes. 410 set_config('allowcoursethemes', 1); 411 412 // Custom fields. 413 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 414 415 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 416 'categoryid' => $fieldcategory->get('id'), 417 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL]]; 418 $field = self::getDataGenerator()->create_custom_field($customfield); 419 420 // Set the required capabilities by the external function 421 $contextid = context_system::instance()->id; 422 $roleid = $this->assignUserCapability('moodle/course:create', $contextid); 423 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 424 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid); 425 426 $category = self::getDataGenerator()->create_category(); 427 428 // Create base categories. 429 $course1['fullname'] = 'Test course 1'; 430 $course1['shortname'] = 'Testcourse1'; 431 $course1['categoryid'] = $category->id; 432 $course2['fullname'] = 'Test course 2'; 433 $course2['shortname'] = 'Testcourse2'; 434 $course2['categoryid'] = $category->id; 435 $course2['idnumber'] = 'testcourse2idnumber'; 436 $course2['summary'] = 'Description for course 2'; 437 $course2['summaryformat'] = FORMAT_MOODLE; 438 $course2['format'] = 'weeks'; 439 $course2['showgrades'] = 1; 440 $course2['newsitems'] = 3; 441 $course2['startdate'] = 1420092000; // 01/01/2015. 442 $course2['enddate'] = 1422669600; // 01/31/2015. 443 $course2['numsections'] = 4; 444 $course2['maxbytes'] = 100000; 445 $course2['showreports'] = 1; 446 $course2['visible'] = 0; 447 $course2['hiddensections'] = 0; 448 $course2['groupmode'] = 0; 449 $course2['groupmodeforce'] = 0; 450 $course2['defaultgroupingid'] = 0; 451 $course2['enablecompletion'] = 1; 452 $course2['completionnotify'] = 1; 453 $course2['lang'] = 'en'; 454 $course2['forcetheme'] = 'classic'; 455 $course2['courseformatoptions'][] = array('name' => 'automaticenddate', 'value' => 0); 456 $course3['fullname'] = 'Test course 3'; 457 $course3['shortname'] = 'Testcourse3'; 458 $course3['categoryid'] = $category->id; 459 $course3['format'] = 'topics'; 460 $course3options = array('numsections' => 8, 461 'hiddensections' => 1, 462 'coursedisplay' => 1); 463 $course3['courseformatoptions'] = array(); 464 foreach ($course3options as $key => $value) { 465 $course3['courseformatoptions'][] = array('name' => $key, 'value' => $value); 466 } 467 $course4['fullname'] = 'Test course with custom fields'; 468 $course4['shortname'] = 'Testcoursecustomfields'; 469 $course4['categoryid'] = $category->id; 470 $course4['customfields'] = [['shortname' => $customfield['shortname'], 'value' => 'Test value']]; 471 $courses = array($course4, $course1, $course2, $course3); 472 473 $createdcourses = core_course_external::create_courses($courses); 474 475 // We need to execute the return values cleaning process to simulate the web service server. 476 $createdcourses = external_api::clean_returnvalue(core_course_external::create_courses_returns(), $createdcourses); 477 478 // Check that right number of courses were created. 479 $this->assertEquals(4, count($createdcourses)); 480 481 // Check that the courses were correctly created. 482 foreach ($createdcourses as $createdcourse) { 483 $courseinfo = course_get_format($createdcourse['id'])->get_course(); 484 485 if ($createdcourse['shortname'] == $course2['shortname']) { 486 $this->assertEquals($courseinfo->fullname, $course2['fullname']); 487 $this->assertEquals($courseinfo->shortname, $course2['shortname']); 488 $this->assertEquals($courseinfo->category, $course2['categoryid']); 489 $this->assertEquals($courseinfo->idnumber, $course2['idnumber']); 490 $this->assertEquals($courseinfo->summary, $course2['summary']); 491 $this->assertEquals($courseinfo->summaryformat, $course2['summaryformat']); 492 $this->assertEquals($courseinfo->format, $course2['format']); 493 $this->assertEquals($courseinfo->showgrades, $course2['showgrades']); 494 $this->assertEquals($courseinfo->newsitems, $course2['newsitems']); 495 $this->assertEquals($courseinfo->startdate, $course2['startdate']); 496 $this->assertEquals($courseinfo->enddate, $course2['enddate']); 497 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(), $course2['numsections']); 498 $this->assertEquals($courseinfo->maxbytes, $course2['maxbytes']); 499 $this->assertEquals($courseinfo->showreports, $course2['showreports']); 500 $this->assertEquals($courseinfo->visible, $course2['visible']); 501 $this->assertEquals($courseinfo->hiddensections, $course2['hiddensections']); 502 $this->assertEquals($courseinfo->groupmode, $course2['groupmode']); 503 $this->assertEquals($courseinfo->groupmodeforce, $course2['groupmodeforce']); 504 $this->assertEquals($courseinfo->defaultgroupingid, $course2['defaultgroupingid']); 505 $this->assertEquals($courseinfo->completionnotify, $course2['completionnotify']); 506 $this->assertEquals($courseinfo->lang, $course2['lang']); 507 $this->assertEquals($courseinfo->theme, $course2['forcetheme']); 508 509 // We enabled completion at the beginning of the test. 510 $this->assertEquals($courseinfo->enablecompletion, $course2['enablecompletion']); 511 512 } else if ($createdcourse['shortname'] == $course1['shortname']) { 513 $courseconfig = get_config('moodlecourse'); 514 $this->assertEquals($courseinfo->fullname, $course1['fullname']); 515 $this->assertEquals($courseinfo->shortname, $course1['shortname']); 516 $this->assertEquals($courseinfo->category, $course1['categoryid']); 517 $this->assertEquals($courseinfo->summaryformat, FORMAT_HTML); 518 $this->assertEquals($courseinfo->format, $courseconfig->format); 519 $this->assertEquals($courseinfo->showgrades, $courseconfig->showgrades); 520 $this->assertEquals($courseinfo->newsitems, $courseconfig->newsitems); 521 $this->assertEquals($courseinfo->maxbytes, $courseconfig->maxbytes); 522 $this->assertEquals($courseinfo->showreports, $courseconfig->showreports); 523 $this->assertEquals($courseinfo->groupmode, $courseconfig->groupmode); 524 $this->assertEquals($courseinfo->groupmodeforce, $courseconfig->groupmodeforce); 525 $this->assertEquals($courseinfo->defaultgroupingid, 0); 526 } else if ($createdcourse['shortname'] == $course3['shortname']) { 527 $this->assertEquals($courseinfo->fullname, $course3['fullname']); 528 $this->assertEquals($courseinfo->shortname, $course3['shortname']); 529 $this->assertEquals($courseinfo->category, $course3['categoryid']); 530 $this->assertEquals($courseinfo->format, $course3['format']); 531 $this->assertEquals($courseinfo->hiddensections, $course3options['hiddensections']); 532 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(), 533 $course3options['numsections']); 534 $this->assertEquals($courseinfo->coursedisplay, $course3options['coursedisplay']); 535 } else if ($createdcourse['shortname'] == $course4['shortname']) { 536 $this->assertEquals($courseinfo->fullname, $course4['fullname']); 537 $this->assertEquals($courseinfo->shortname, $course4['shortname']); 538 $this->assertEquals($courseinfo->category, $course4['categoryid']); 539 540 $handler = core_course\customfield\course_handler::create(); 541 $customfields = $handler->export_instance_data_object($createdcourse['id']); 542 $this->assertEquals((object)['test' => 'Test value'], $customfields); 543 } else { 544 throw new moodle_exception('Unexpected shortname'); 545 } 546 } 547 548 // Call without required capability 549 $this->unassignUserCapability('moodle/course:create', $contextid, $roleid); 550 $this->expectException('required_capability_exception'); 551 $createdsubcats = core_course_external::create_courses($courses); 552 } 553 554 /** 555 * Data provider for testing empty fields produce expected exceptions 556 * 557 * @see test_create_courses_empty_field 558 * @see test_update_courses_empty_field 559 * 560 * @return array 561 */ 562 public function course_empty_field_provider(): array { 563 return [ 564 [[ 565 'fullname' => '', 566 'shortname' => 'ws101', 567 ], 'fullname'], 568 [[ 569 'fullname' => ' ', 570 'shortname' => 'ws101', 571 ], 'fullname'], 572 [[ 573 'fullname' => 'Web Services', 574 'shortname' => '', 575 ], 'shortname'], 576 [[ 577 'fullname' => 'Web Services', 578 'shortname' => ' ', 579 ], 'shortname'], 580 ]; 581 } 582 583 /** 584 * Test creating courses with empty fields throws an exception 585 * 586 * @param array $course 587 * @param string $expectedemptyfield 588 * 589 * @dataProvider course_empty_field_provider 590 */ 591 public function test_create_courses_empty_field(array $course, string $expectedemptyfield): void { 592 $this->resetAfterTest(); 593 $this->setAdminUser(); 594 595 // Create a category for the new course. 596 $course['categoryid'] = $this->getDataGenerator()->create_category()->id; 597 598 $this->expectException(moodle_exception::class); 599 $this->expectExceptionMessageMatches("/{$expectedemptyfield}/"); 600 core_course_external::create_courses([$course]); 601 } 602 603 /** 604 * Test updating courses with empty fields returns warnings 605 * 606 * @param array $course 607 * @param string $expectedemptyfield 608 * 609 * @dataProvider course_empty_field_provider 610 */ 611 public function test_update_courses_empty_field(array $course, string $expectedemptyfield): void { 612 $this->resetAfterTest(); 613 $this->setAdminUser(); 614 615 // Create a course to update. 616 $course['id'] = $this->getDataGenerator()->create_course()->id; 617 618 $result = core_course_external::update_courses([$course]); 619 $result = core_course_external::clean_returnvalue(core_course_external::update_courses_returns(), $result); 620 621 $this->assertCount(1, $result['warnings']); 622 623 $warning = reset($result['warnings']); 624 $this->assertEquals('errorinvalidparam', $warning['warningcode']); 625 $this->assertStringContainsString($expectedemptyfield, $warning['message']); 626 } 627 628 /** 629 * Test delete_courses 630 */ 631 public function test_delete_courses() { 632 global $DB, $USER; 633 634 $this->resetAfterTest(true); 635 636 // Admin can delete a course. 637 $this->setAdminUser(); 638 // Validate_context() will fail as the email is not set by $this->setAdminUser(). 639 $USER->email = 'emailtopass@example.com'; 640 641 $course1 = self::getDataGenerator()->create_course(); 642 $course2 = self::getDataGenerator()->create_course(); 643 $course3 = self::getDataGenerator()->create_course(); 644 645 // Delete courses. 646 $result = core_course_external::delete_courses(array($course1->id, $course2->id)); 647 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 648 // Check for 0 warnings. 649 $this->assertEquals(0, count($result['warnings'])); 650 651 // Check $course 1 and 2 are deleted. 652 $notdeletedcount = $DB->count_records_select('course', 653 'id IN ( ' . $course1->id . ',' . $course2->id . ')'); 654 $this->assertEquals(0, $notdeletedcount); 655 656 // Try to delete non-existent course. 657 $result = core_course_external::delete_courses(array($course1->id)); 658 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 659 // Check for 1 warnings. 660 $this->assertEquals(1, count($result['warnings'])); 661 662 // Try to delete Frontpage course. 663 $result = core_course_external::delete_courses(array(0)); 664 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 665 // Check for 1 warnings. 666 $this->assertEquals(1, count($result['warnings'])); 667 668 // Fail when the user has access to course (enrolled) but does not have permission or is not admin. 669 $student1 = self::getDataGenerator()->create_user(); 670 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 671 $this->getDataGenerator()->enrol_user($student1->id, 672 $course3->id, 673 $studentrole->id); 674 $this->setUser($student1); 675 $result = core_course_external::delete_courses(array($course3->id)); 676 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 677 // Check for 1 warnings. 678 $this->assertEquals(1, count($result['warnings'])); 679 680 // Fail when the user is not allow to access the course (enrolled) or is not admin. 681 $this->setGuestUser(); 682 $this->expectException('require_login_exception'); 683 684 $result = core_course_external::delete_courses(array($course3->id)); 685 $result = external_api::clean_returnvalue(core_course_external::delete_courses_returns(), $result); 686 } 687 688 /** 689 * Test get_courses 690 */ 691 public function test_get_courses () { 692 global $DB; 693 694 $this->resetAfterTest(true); 695 696 $generatedcourses = array(); 697 $coursedata['idnumber'] = 'idnumbercourse1'; 698 // Adding tags here to check that format_string is applied. 699 $coursedata['fullname'] = '<b>Course 1 for PHPunit test</b>'; 700 $coursedata['shortname'] = '<b>Course 1 for PHPunit test</b>'; 701 $coursedata['summary'] = 'Course 1 description'; 702 $coursedata['summaryformat'] = FORMAT_MOODLE; 703 $course1 = self::getDataGenerator()->create_course($coursedata); 704 705 $fieldcategory = self::getDataGenerator()->create_custom_field_category( 706 ['name' => 'Other fields']); 707 708 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 709 'categoryid' => $fieldcategory->get('id')]; 710 $field = self::getDataGenerator()->create_custom_field($customfield); 711 712 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; 713 714 $generatedcourses[$course1->id] = $course1; 715 $course2 = self::getDataGenerator()->create_course(); 716 $generatedcourses[$course2->id] = $course2; 717 $course3 = self::getDataGenerator()->create_course(array('format' => 'topics')); 718 $generatedcourses[$course3->id] = $course3; 719 $course4 = self::getDataGenerator()->create_course(['customfields' => [$customfieldvalue]]); 720 $generatedcourses[$course4->id] = $course4; 721 722 // Set the required capabilities by the external function. 723 $context = context_system::instance(); 724 $roleid = $this->assignUserCapability('moodle/course:view', $context->id); 725 $this->assignUserCapability('moodle/course:update', 726 context_course::instance($course1->id)->id, $roleid); 727 $this->assignUserCapability('moodle/course:update', 728 context_course::instance($course2->id)->id, $roleid); 729 $this->assignUserCapability('moodle/course:update', 730 context_course::instance($course3->id)->id, $roleid); 731 $this->assignUserCapability('moodle/course:update', 732 context_course::instance($course4->id)->id, $roleid); 733 734 $courses = core_course_external::get_courses(array('ids' => 735 array($course1->id, $course2->id, $course4->id))); 736 737 // We need to execute the return values cleaning process to simulate the web service server. 738 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 739 740 // Check we retrieve the good total number of courses. 741 $this->assertEquals(3, count($courses)); 742 743 foreach ($courses as $course) { 744 $coursecontext = context_course::instance($course['id']); 745 $dbcourse = $generatedcourses[$course['id']]; 746 $this->assertEquals($course['idnumber'], $dbcourse->idnumber); 747 $this->assertEquals($course['fullname'], external_format_string($dbcourse->fullname, $coursecontext->id)); 748 $this->assertEquals($course['displayname'], external_format_string(get_course_display_name_for_list($dbcourse), 749 $coursecontext->id)); 750 // Summary was converted to the HTML format. 751 $this->assertEquals($course['summary'], format_text($dbcourse->summary, FORMAT_MOODLE, array('para' => false))); 752 $this->assertEquals($course['summaryformat'], FORMAT_HTML); 753 $this->assertEquals($course['shortname'], external_format_string($dbcourse->shortname, $coursecontext->id)); 754 $this->assertEquals($course['categoryid'], $dbcourse->category); 755 $this->assertEquals($course['format'], $dbcourse->format); 756 $this->assertEquals($course['showgrades'], $dbcourse->showgrades); 757 $this->assertEquals($course['newsitems'], $dbcourse->newsitems); 758 $this->assertEquals($course['startdate'], $dbcourse->startdate); 759 $this->assertEquals($course['enddate'], $dbcourse->enddate); 760 $this->assertEquals($course['numsections'], course_get_format($dbcourse)->get_last_section_number()); 761 $this->assertEquals($course['maxbytes'], $dbcourse->maxbytes); 762 $this->assertEquals($course['showreports'], $dbcourse->showreports); 763 $this->assertEquals($course['visible'], $dbcourse->visible); 764 $this->assertEquals($course['hiddensections'], $dbcourse->hiddensections); 765 $this->assertEquals($course['groupmode'], $dbcourse->groupmode); 766 $this->assertEquals($course['groupmodeforce'], $dbcourse->groupmodeforce); 767 $this->assertEquals($course['defaultgroupingid'], $dbcourse->defaultgroupingid); 768 $this->assertEquals($course['completionnotify'], $dbcourse->completionnotify); 769 $this->assertEquals($course['lang'], $dbcourse->lang); 770 $this->assertEquals($course['forcetheme'], $dbcourse->theme); 771 $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion); 772 if ($dbcourse->format === 'topics') { 773 $this->assertEquals($course['courseformatoptions'], array( 774 array('name' => 'hiddensections', 'value' => $dbcourse->hiddensections), 775 array('name' => 'coursedisplay', 'value' => $dbcourse->coursedisplay), 776 )); 777 } 778 779 // Assert custom field that we previously added to test course 4. 780 if ($dbcourse->id == $course4->id) { 781 $this->assertEquals([ 782 'shortname' => $customfield['shortname'], 783 'name' => $customfield['name'], 784 'type' => $customfield['type'], 785 'value' => $customfieldvalue['value'], 786 'valueraw' => $customfieldvalue['value'], 787 ], $course['customfields'][0]); 788 } 789 } 790 791 // Get all courses in the DB 792 $courses = core_course_external::get_courses(array()); 793 794 // We need to execute the return values cleaning process to simulate the web service server. 795 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 796 797 $this->assertEquals($DB->count_records('course'), count($courses)); 798 } 799 800 /** 801 * Test retrieving courses returns custom field data 802 */ 803 public function test_get_courses_customfields(): void { 804 $this->resetAfterTest(); 805 $this->setAdminUser(); 806 807 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 808 $datefield = $this->getDataGenerator()->create_custom_field([ 809 'categoryid' => $fieldcategory->get('id'), 810 'shortname' => 'mydate', 811 'name' => 'My date', 812 'type' => 'date', 813 ]); 814 815 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 816 [ 817 'shortname' => $datefield->get('shortname'), 818 'value' => 1580389200, // 30/01/2020 13:00 GMT. 819 ], 820 ]]); 821 822 $courses = external_api::clean_returnvalue( 823 core_course_external::get_courses_returns(), 824 core_course_external::get_courses(['ids' => [$newcourse->id]]) 825 ); 826 827 $this->assertCount(1, $courses); 828 $course = reset($courses); 829 830 $this->assertArrayHasKey('customfields', $course); 831 $this->assertCount(1, $course['customfields']); 832 833 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 834 $this->assertEquals([ 835 'name' => $datefield->get('name'), 836 'shortname' => $datefield->get('shortname'), 837 'type' => $datefield->get('type'), 838 'value' => userdate(1580389200), 839 'valueraw' => 1580389200, 840 ], reset($course['customfields'])); 841 } 842 843 /** 844 * Test get_courses without capability 845 */ 846 public function test_get_courses_without_capability() { 847 $this->resetAfterTest(true); 848 849 $course1 = $this->getDataGenerator()->create_course(); 850 $this->setUser($this->getDataGenerator()->create_user()); 851 852 // No permissions are required to get the site course. 853 $courses = core_course_external::get_courses(array('ids' => [SITEID])); 854 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 855 856 $this->assertEquals(1, count($courses)); 857 $this->assertEquals('PHPUnit test site', $courses[0]['fullname']); 858 $this->assertEquals('site', $courses[0]['format']); 859 860 // Requesting course without being enrolled or capability to view it will throw an exception. 861 try { 862 core_course_external::get_courses(array('ids' => [$course1->id])); 863 $this->fail('Exception expected'); 864 } catch (moodle_exception $e) { 865 $this->assertEquals(1, preg_match('/Course or activity not accessible. \(Not enrolled\)/', $e->getMessage())); 866 } 867 } 868 869 /** 870 * Test search_courses 871 */ 872 public function test_search_courses () { 873 874 global $DB; 875 876 $this->resetAfterTest(true); 877 $this->setAdminUser(); 878 $generatedcourses = array(); 879 $coursedata1['fullname'] = 'FIRST COURSE'; 880 $course1 = self::getDataGenerator()->create_course($coursedata1); 881 882 $page = new moodle_page(); 883 $page->set_course($course1); 884 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 885 886 $coursedata2['fullname'] = 'SECOND COURSE'; 887 $course2 = self::getDataGenerator()->create_course($coursedata2); 888 889 $page = new moodle_page(); 890 $page->set_course($course2); 891 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 892 893 // Search by name. 894 $results = core_course_external::search_courses('search', 'FIRST'); 895 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 896 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 897 $this->assertCount(1, $results['courses']); 898 899 // Create the forum. 900 $record = new stdClass(); 901 $record->introformat = FORMAT_HTML; 902 $record->course = $course2->id; 903 // Set Aggregate type = Average of ratings. 904 $forum = self::getDataGenerator()->create_module('forum', $record); 905 906 // Search by module. 907 $results = core_course_external::search_courses('modulelist', 'forum'); 908 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 909 $this->assertEquals(1, $results['total']); 910 911 // Enable coursetag option. 912 set_config('block_tags_showcoursetags', true); 913 // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2. 914 core_tag_tag::set_item_tags('core', 'course', $course2->id, context_course::instance($course2->id), 915 array('TAG-LABEL ON SECOND COURSE')); 916 $taginstance = $DB->get_record('tag_instance', 917 array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST); 918 919 // Search by tagid. 920 $results = core_course_external::search_courses('tagid', $taginstance->tagid); 921 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 922 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 923 924 // Search by block (use news_items default block). 925 $blockid = $DB->get_field('block', 'id', array('name' => 'news_items')); 926 $results = core_course_external::search_courses('blocklist', $blockid); 927 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 928 $this->assertEquals(2, $results['total']); 929 930 // Now as a normal user. 931 $user = self::getDataGenerator()->create_user(); 932 933 // Add a 3rd, hidden, course we shouldn't see, even when enrolled as student. 934 $coursedata3['fullname'] = 'HIDDEN COURSE'; 935 $coursedata3['visible'] = 0; 936 $course3 = self::getDataGenerator()->create_course($coursedata3); 937 $this->getDataGenerator()->enrol_user($user->id, $course3->id, 'student'); 938 939 $this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student'); 940 $this->setUser($user); 941 942 $results = core_course_external::search_courses('search', 'FIRST'); 943 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 944 $this->assertCount(1, $results['courses']); 945 $this->assertEquals(1, $results['total']); 946 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 947 948 // Check that we can see all courses without the limit to enrolled setting. 949 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 0); 950 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 951 $this->assertCount(2, $results['courses']); 952 $this->assertEquals(2, $results['total']); 953 954 // Check that we only see our enrolled course when limiting. 955 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 1); 956 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 957 $this->assertCount(1, $results['courses']); 958 $this->assertEquals(1, $results['total']); 959 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 960 961 // Search by block (use news_items default block). Should fail (only admins allowed). 962 $this->expectException('required_capability_exception'); 963 $results = core_course_external::search_courses('blocklist', $blockid); 964 } 965 966 /** 967 * Test searching for courses returns custom field data 968 */ 969 public function test_search_courses_customfields(): void { 970 $this->resetAfterTest(); 971 $this->setAdminUser(); 972 973 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 974 $datefield = $this->getDataGenerator()->create_custom_field([ 975 'categoryid' => $fieldcategory->get('id'), 976 'shortname' => 'mydate', 977 'name' => 'My date', 978 'type' => 'date', 979 ]); 980 981 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 982 [ 983 'shortname' => $datefield->get('shortname'), 984 'value' => 1580389200, // 30/01/2020 13:00 GMT. 985 ], 986 ]]); 987 988 $result = external_api::clean_returnvalue( 989 core_course_external::search_courses_returns(), 990 core_course_external::search_courses('search', $newcourse->shortname) 991 ); 992 993 $this->assertCount(1, $result['courses']); 994 $course = reset($result['courses']); 995 996 $this->assertArrayHasKey('customfields', $course); 997 $this->assertCount(1, $course['customfields']); 998 999 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 1000 $this->assertEquals([ 1001 'name' => $datefield->get('name'), 1002 'shortname' => $datefield->get('shortname'), 1003 'type' => $datefield->get('type'), 1004 'value' => userdate(1580389200), 1005 'valueraw' => 1580389200, 1006 ], reset($course['customfields'])); 1007 } 1008 1009 /** 1010 * Create a course with contents 1011 * @return array A list with the course object and course modules objects 1012 */ 1013 private function prepare_get_course_contents_test() { 1014 global $DB, $CFG; 1015 1016 $CFG->allowstealth = 1; // Allow stealth activities. 1017 $CFG->enablecompletion = true; 1018 // Course with 4 sections (apart from the main section), with completion and not displaying hidden sections. 1019 $course = self::getDataGenerator()->create_course(['numsections' => 4, 'enablecompletion' => 1, 'hiddensections' => 1]); 1020 1021 $forumdescription = 'This is the forum description'; 1022 $forum = $this->getDataGenerator()->create_module('forum', 1023 array('course' => $course->id, 'intro' => $forumdescription, 'trackingtype' => 2), 1024 array('showdescription' => true, 'completion' => COMPLETION_TRACKING_MANUAL)); 1025 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 1026 // Add discussions to the tracking forced forum. 1027 $record = new stdClass(); 1028 $record->course = $course->id; 1029 $record->userid = 0; 1030 $record->forum = $forum->id; 1031 $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1032 $data = $this->getDataGenerator()->create_module('data', 1033 array('assessed' => 1, 'scale' => 100, 'course' => $course->id, 'completion' => 2, 'completionentries' => 3)); 1034 $datacm = get_coursemodule_from_instance('data', $data->id); 1035 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1036 $pagecm = get_coursemodule_from_instance('page', $page->id); 1037 // This is an stealth page (set by visibleoncoursepage). 1038 $pagestealth = $this->getDataGenerator()->create_module('page', array('course' => $course->id, 'visibleoncoursepage' => 0)); 1039 $labeldescription = 'This is a very long label to test if more than 50 characters are returned. 1040 So bla bla bla bla <b>bold bold bold</b> bla bla bla bla.'; 1041 $label = $this->getDataGenerator()->create_module('label', array('course' => $course->id, 1042 'intro' => $labeldescription, 'completion' => COMPLETION_TRACKING_MANUAL)); 1043 $labelcm = get_coursemodule_from_instance('label', $label->id); 1044 $tomorrow = time() + DAYSECS; 1045 // Module with availability restrictions not met. 1046 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '},' 1047 .'{"type":"completion","cm":' . $label->cmid .',"e":1}],"showc":[true,true]}'; 1048 $url = $this->getDataGenerator()->create_module('url', 1049 array('course' => $course->id, 'name' => 'URL: % & $ ../', 'section' => 2, 'display' => RESOURCELIB_DISPLAY_POPUP, 1050 'popupwidth' => 100, 'popupheight' => 100), 1051 array('availability' => $availability)); 1052 $urlcm = get_coursemodule_from_instance('url', $url->id); 1053 // Module for the last section. 1054 $this->getDataGenerator()->create_module('url', 1055 array('course' => $course->id, 'name' => 'URL for last section', 'section' => 3)); 1056 // Module for section 1 with availability restrictions met. 1057 $yesterday = time() - DAYSECS; 1058 $this->getDataGenerator()->create_module('url', 1059 array('course' => $course->id, 'name' => 'URL restrictions met', 'section' => 1), 1060 array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":'. $yesterday .'}],"showc":[true]}')); 1061 1062 // Set the required capabilities by the external function. 1063 $context = context_course::instance($course->id); 1064 $roleid = $this->assignUserCapability('moodle/course:view', $context->id); 1065 $this->assignUserCapability('moodle/course:update', $context->id, $roleid); 1066 $this->assignUserCapability('mod/data:view', $context->id, $roleid); 1067 1068 $conditions = array('course' => $course->id, 'section' => 2); 1069 $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions); 1070 1071 // Add date availability condition not met for section 3. 1072 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '}],"showc":[true]}'; 1073 $DB->set_field('course_sections', 'availability', $availability, 1074 array('course' => $course->id, 'section' => 3)); 1075 1076 // Create resource for last section. 1077 $pageinhiddensection = $this->getDataGenerator()->create_module('page', 1078 array('course' => $course->id, 'name' => 'Page in hidden section', 'section' => 4)); 1079 // Set not visible last section. 1080 $DB->set_field('course_sections', 'visible', 0, 1081 array('course' => $course->id, 'section' => 4)); 1082 1083 $forumcompleteauto = $this->getDataGenerator()->create_module('forum', 1084 array('course' => $course->id, 'intro' => 'forum completion tracking auto', 'trackingtype' => 2), 1085 array('showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC)); 1086 $forumcompleteautocm = get_coursemodule_from_id('forum', $forumcompleteauto->cmid); 1087 1088 rebuild_course_cache($course->id, true); 1089 1090 return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm); 1091 } 1092 1093 /** 1094 * Test get_course_contents 1095 */ 1096 public function test_get_course_contents() { 1097 global $CFG; 1098 $this->resetAfterTest(true); 1099 1100 $CFG->forum_allowforcedreadtracking = 1; 1101 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1102 1103 // We first run the test as admin. 1104 $this->setAdminUser(); 1105 $sections = core_course_external::get_course_contents($course->id, array()); 1106 // We need to execute the return values cleaning process to simulate the web service server. 1107 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1108 1109 $modinfo = get_fast_modinfo($course); 1110 $testexecuted = 0; 1111 foreach ($sections[0]['modules'] as $module) { 1112 if ($module['id'] == $forumcm->id and $module['modname'] == 'forum') { 1113 $cm = $modinfo->cms[$forumcm->id]; 1114 $formattedtext = format_text($cm->content, FORMAT_HTML, 1115 array('noclean' => true, 'para' => false, 'filter' => false)); 1116 $this->assertEquals($formattedtext, $module['description']); 1117 $this->assertEquals($forumcm->instance, $module['instance']); 1118 $this->assertEquals(context_module::instance($forumcm->id)->id, $module['contextid']); 1119 $this->assertStringContainsString('1 unread post', $module['afterlink']); 1120 $this->assertFalse($module['noviewlink']); 1121 $this->assertNotEmpty($module['description']); // Module showdescription is on. 1122 $testexecuted = $testexecuted + 2; 1123 } else if ($module['id'] == $labelcm->id and $module['modname'] == 'label') { 1124 $cm = $modinfo->cms[$labelcm->id]; 1125 $formattedtext = format_text($cm->content, FORMAT_HTML, 1126 array('noclean' => true, 'para' => false, 'filter' => false)); 1127 $this->assertEquals($formattedtext, $module['description']); 1128 $this->assertEquals($labelcm->instance, $module['instance']); 1129 $this->assertEquals(context_module::instance($labelcm->id)->id, $module['contextid']); 1130 $this->assertTrue($module['noviewlink']); 1131 $this->assertNotEmpty($module['description']); // Label always prints the description. 1132 $testexecuted = $testexecuted + 1; 1133 } else if ($module['id'] == $datacm->id and $module['modname'] == 'data') { 1134 $this->assertStringContainsString('customcompletionrules', $module['customdata']); 1135 $this->assertFalse($module['noviewlink']); 1136 $this->assertArrayNotHasKey('description', $module); 1137 $testexecuted = $testexecuted + 1; 1138 } 1139 } 1140 foreach ($sections[2]['modules'] as $module) { 1141 if ($module['id'] == $urlcm->id and $module['modname'] == 'url') { 1142 $this->assertStringContainsString('width=100,height=100', $module['onclick']); 1143 $testexecuted = $testexecuted + 1; 1144 } 1145 } 1146 1147 $CFG->forum_allowforcedreadtracking = 0; // Recover original value. 1148 forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests. 1149 1150 $this->assertEquals(5, $testexecuted); 1151 $this->assertEquals(0, $sections[0]['section']); 1152 1153 $this->assertCount(6, $sections[0]['modules']); 1154 $this->assertCount(1, $sections[1]['modules']); 1155 $this->assertCount(1, $sections[2]['modules']); 1156 $this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions. 1157 $this->assertCount(1, $sections[4]['modules']); // One module for the hidden section with a visible activity. 1158 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1159 $this->assertEquals(1, $sections[1]['section']); 1160 $this->assertEquals(2, $sections[2]['section']); 1161 $this->assertEquals(3, $sections[3]['section']); 1162 $this->assertEquals(4, $sections[4]['section']); 1163 $this->assertStringContainsString('<iframe', $sections[2]['summary']); 1164 $this->assertStringContainsString('</iframe>', $sections[2]['summary']); 1165 $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']); 1166 try { 1167 $sections = core_course_external::get_course_contents($course->id, 1168 array(array("name" => "invalid", "value" => 1))); 1169 $this->fail('Exception expected due to invalid option.'); 1170 } catch (moodle_exception $e) { 1171 $this->assertEquals('errorinvalidparam', $e->errorcode); 1172 } 1173 } 1174 1175 1176 /** 1177 * Test get_course_contents as student 1178 */ 1179 public function test_get_course_contents_student() { 1180 global $DB; 1181 $this->resetAfterTest(true); 1182 1183 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1184 1185 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1186 $user = self::getDataGenerator()->create_user(); 1187 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1188 $this->setUser($user); 1189 1190 $sections = core_course_external::get_course_contents($course->id, array()); 1191 // We need to execute the return values cleaning process to simulate the web service server. 1192 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1193 1194 $this->assertCount(4, $sections); // Nothing for the not visible section. 1195 $this->assertCount(6, $sections[0]['modules']); 1196 $this->assertCount(1, $sections[1]['modules']); 1197 $this->assertCount(1, $sections[2]['modules']); 1198 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1199 1200 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1201 $this->assertEquals(1, $sections[1]['section']); 1202 $this->assertEquals(2, $sections[2]['section']); 1203 $this->assertEquals(3, $sections[3]['section']); 1204 // The module with the availability restriction met is returning contents. 1205 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1206 // The module with the availability restriction not met is not returning contents. 1207 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1208 1209 // Now include flag for returning stealth information (fake section). 1210 $sections = core_course_external::get_course_contents($course->id, 1211 array(array("name" => "includestealthmodules", "value" => 1))); 1212 // We need to execute the return values cleaning process to simulate the web service server. 1213 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1214 1215 $this->assertCount(5, $sections); // Include fake section with stealth activities. 1216 $this->assertCount(6, $sections[0]['modules']); 1217 $this->assertCount(1, $sections[1]['modules']); 1218 $this->assertCount(1, $sections[2]['modules']); 1219 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1220 $this->assertCount(1, $sections[4]['modules']); // One stealth module. 1221 $this->assertEquals(-1, $sections[4]['id']); 1222 } 1223 1224 /** 1225 * Test get_course_contents excluding modules 1226 */ 1227 public function test_get_course_contents_excluding_modules() { 1228 $this->resetAfterTest(true); 1229 1230 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1231 1232 // Test exclude modules. 1233 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludemodules", "value" => 1))); 1234 1235 // We need to execute the return values cleaning process to simulate the web service server. 1236 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1237 1238 $this->assertEmpty($sections[0]['modules']); 1239 $this->assertEmpty($sections[1]['modules']); 1240 } 1241 1242 /** 1243 * Test get_course_contents excluding contents 1244 */ 1245 public function test_get_course_contents_excluding_contents() { 1246 $this->resetAfterTest(true); 1247 1248 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1249 1250 // Test exclude modules. 1251 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludecontents", "value" => 1))); 1252 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 foreach ($sections as $section) { 1257 foreach ($section['modules'] as $module) { 1258 // Only resources return contents. 1259 if (isset($module['contents'])) { 1260 $this->assertEmpty($module['contents']); 1261 } 1262 } 1263 } 1264 } 1265 1266 /** 1267 * Test get_course_contents filtering by section number 1268 */ 1269 public function test_get_course_contents_section_number() { 1270 $this->resetAfterTest(true); 1271 1272 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1273 1274 // Test exclude modules. 1275 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "sectionnumber", "value" => 0))); 1276 1277 // We need to execute the return values cleaning process to simulate the web service server. 1278 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1279 1280 $this->assertCount(1, $sections); 1281 $this->assertCount(6, $sections[0]['modules']); 1282 } 1283 1284 /** 1285 * Test get_course_contents filtering by cmid 1286 */ 1287 public function test_get_course_contents_cmid() { 1288 $this->resetAfterTest(true); 1289 1290 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1291 1292 // Test exclude modules. 1293 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "cmid", "value" => $forumcm->id))); 1294 1295 // We need to execute the return values cleaning process to simulate the web service server. 1296 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1297 1298 $this->assertCount(4, $sections); 1299 $this->assertCount(1, $sections[0]['modules']); 1300 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1301 } 1302 1303 1304 /** 1305 * Test get_course_contents filtering by cmid and section 1306 */ 1307 public function test_get_course_contents_section_cmid() { 1308 $this->resetAfterTest(true); 1309 1310 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1311 1312 // Test exclude modules. 1313 $sections = core_course_external::get_course_contents($course->id, array( 1314 array("name" => "cmid", "value" => $forumcm->id), 1315 array("name" => "sectionnumber", "value" => 0) 1316 )); 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(1, $sections[0]['modules']); 1323 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1324 } 1325 1326 /** 1327 * Test get_course_contents filtering by modname 1328 */ 1329 public function test_get_course_contents_modname() { 1330 $this->resetAfterTest(true); 1331 1332 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1333 1334 // Test exclude modules. 1335 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "modname", "value" => "forum"))); 1336 1337 // We need to execute the return values cleaning process to simulate the web service server. 1338 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1339 1340 $this->assertCount(4, $sections); 1341 $this->assertCount(2, $sections[0]['modules']); 1342 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1343 } 1344 1345 /** 1346 * Test get_course_contents filtering by modname 1347 */ 1348 public function test_get_course_contents_modid() { 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" => "modname", "value" => "page"), 1356 array("name" => "modid", "value" => $pagecm->instance), 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(4, $sections); 1363 $this->assertCount(1, $sections[0]['modules']); 1364 $this->assertEquals("page", $sections[0]['modules'][0]["modname"]); 1365 $this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]); 1366 } 1367 1368 /** 1369 * Test get course contents completion manual 1370 */ 1371 public function test_get_course_contents_completion_manual() { 1372 global $CFG; 1373 $this->resetAfterTest(true); 1374 1375 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) = 1376 $this->prepare_get_course_contents_test(); 1377 availability_completion\condition::wipe_static_cache(); 1378 1379 // Test activity not completed yet. 1380 $result = core_course_external::get_course_contents($course->id, array( 1381 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1382 // We need to execute the return values cleaning process to simulate the web service server. 1383 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1384 1385 $completiondata = $result[0]['modules'][0]["completiondata"]; 1386 $this->assertCount(1, $result[0]['modules']); 1387 $this->assertEquals("forum", $result[0]['modules'][0]["modname"]); 1388 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1389 $this->assertEquals(0, $completiondata['state']); 1390 $this->assertEquals(0, $completiondata['timecompleted']); 1391 $this->assertEmpty($completiondata['overrideby']); 1392 $this->assertFalse($completiondata['valueused']); 1393 $this->assertTrue($completiondata['hascompletion']); 1394 $this->assertFalse($completiondata['isautomatic']); 1395 $this->assertFalse($completiondata['istrackeduser']); 1396 $this->assertTrue($completiondata['uservisible']); 1397 1398 // Set activity completed. 1399 core_completion_external::update_activity_completion_status_manually($forumcm->id, true); 1400 1401 $result = core_course_external::get_course_contents($course->id, array( 1402 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1403 // We need to execute the return values cleaning process to simulate the web service server. 1404 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1405 1406 $this->assertEquals(COMPLETION_COMPLETE, $result[0]['modules'][0]["completiondata"]['state']); 1407 $this->assertNotEmpty($result[0]['modules'][0]["completiondata"]['timecompleted']); 1408 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']); 1409 1410 // Test activity with completion value that is used in an availability condition. 1411 $result = core_course_external::get_course_contents($course->id, array( 1412 array("name" => "modname", "value" => "label"), array("name" => "modid", "value" => $labelcm->instance))); 1413 // We need to execute the return values cleaning process to simulate the web service server. 1414 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1415 1416 $completiondata = $result[0]['modules'][0]["completiondata"]; 1417 $this->assertCount(1, $result[0]['modules']); 1418 $this->assertEquals("label", $result[0]['modules'][0]["modname"]); 1419 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1420 $this->assertEquals(0, $completiondata['state']); 1421 $this->assertEquals(0, $completiondata['timecompleted']); 1422 $this->assertEmpty($completiondata['overrideby']); 1423 $this->assertTrue($completiondata['valueused']); 1424 $this->assertTrue($completiondata['hascompletion']); 1425 $this->assertFalse($completiondata['isautomatic']); 1426 $this->assertFalse($completiondata['istrackeduser']); 1427 $this->assertTrue($completiondata['uservisible']); 1428 1429 // Disable completion. 1430 $CFG->enablecompletion = 0; 1431 $result = core_course_external::get_course_contents($course->id, array( 1432 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1433 // We need to execute the return values cleaning process to simulate the web service server. 1434 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1435 1436 $this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]); 1437 } 1438 1439 /** 1440 * Test get course contents completion auto 1441 */ 1442 public function test_get_course_contents_completion_auto() { 1443 global $CFG; 1444 $this->resetAfterTest(true); 1445 1446 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) = 1447 $this->prepare_get_course_contents_test(); 1448 availability_completion\condition::wipe_static_cache(); 1449 1450 // Test activity not completed yet. 1451 $result = core_course_external::get_course_contents($course->id, [ 1452 [ 1453 "name" => "modname", 1454 "value" => "forum" 1455 ], 1456 [ 1457 "name" => "modid", 1458 "value" => $forumcompleteautocm->instance 1459 ] 1460 ]); 1461 // We need to execute the return values cleaning process to simulate the web service server. 1462 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1463 1464 $forummod = $result[0]['modules'][0]; 1465 $completiondata = $forummod["completiondata"]; 1466 $this->assertCount(1, $result[0]['modules']); 1467 $this->assertEquals("forum", $forummod["modname"]); 1468 $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $forummod["completion"]); 1469 $this->assertEquals(0, $completiondata['state']); 1470 $this->assertEquals(0, $completiondata['timecompleted']); 1471 $this->assertEmpty($completiondata['overrideby']); 1472 $this->assertFalse($completiondata['valueused']); 1473 $this->assertTrue($completiondata['hascompletion']); 1474 $this->assertTrue($completiondata['isautomatic']); 1475 $this->assertFalse($completiondata['istrackeduser']); 1476 $this->assertTrue($completiondata['uservisible']); 1477 $this->assertCount(1, $completiondata['details']); 1478 } 1479 1480 /** 1481 * Test mimetype is returned for resources with showtype set. 1482 */ 1483 public function test_get_course_contents_including_mimetype() { 1484 $this->resetAfterTest(true); 1485 1486 $this->setAdminUser(); 1487 $course = self::getDataGenerator()->create_course(); 1488 1489 $record = new stdClass(); 1490 $record->course = $course->id; 1491 $record->showtype = 1; 1492 $resource = self::getDataGenerator()->create_module('resource', $record); 1493 1494 $result = core_course_external::get_course_contents($course->id); 1495 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1496 $this->assertCount(1, $result[0]['modules']); // One module, first section. 1497 $customdata = json_decode($result[0]['modules'][0]['customdata']); 1498 $displayoptions = unserialize($customdata->displayoptions); 1499 $this->assertEquals('text/plain', $displayoptions['filedetails']['mimetype']); 1500 } 1501 1502 /** 1503 * Test contents info is returned. 1504 */ 1505 public function test_get_course_contents_contentsinfo() { 1506 global $USER; 1507 1508 $this->resetAfterTest(true); 1509 $this->setAdminUser(); 1510 $timenow = time(); 1511 1512 $course = self::getDataGenerator()->create_course(); 1513 1514 $record = new stdClass(); 1515 $record->course = $course->id; 1516 // One resource with one file. 1517 $resource1 = self::getDataGenerator()->create_module('resource', $record); 1518 1519 // More type of files. 1520 $record->files = file_get_unused_draft_itemid(); 1521 $usercontext = context_user::instance($USER->id); 1522 $extensions = array('txt', 'png', 'pdf'); 1523 $fs = get_file_storage(); 1524 foreach ($extensions as $key => $extension) { 1525 // Add actual file there. 1526 $filerecord = array('component' => 'user', 'filearea' => 'draft', 1527 'contextid' => $usercontext->id, 'itemid' => $record->files, 1528 'filename' => 'resource' . $key . '.' . $extension, 'filepath' => '/'); 1529 $fs->create_file_from_string($filerecord, 'Test resource ' . $key . ' file'); 1530 } 1531 1532 // Create file reference. 1533 $repos = repository::get_instances(array('type' => 'user')); 1534 $userrepository = reset($repos); 1535 1536 // Create a user private file. 1537 $userfilerecord = new stdClass; 1538 $userfilerecord->contextid = $usercontext->id; 1539 $userfilerecord->component = 'user'; 1540 $userfilerecord->filearea = 'private'; 1541 $userfilerecord->itemid = 0; 1542 $userfilerecord->filepath = '/'; 1543 $userfilerecord->filename = 'userfile.txt'; 1544 $userfilerecord->source = 'test'; 1545 $userfile = $fs->create_file_from_string($userfilerecord, 'User file content'); 1546 $userfileref = $fs->pack_reference($userfilerecord); 1547 1548 // Clone latest "normal" file. 1549 $filerefrecord = clone (object) $filerecord; 1550 $filerefrecord->filename = 'testref.txt'; 1551 $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref); 1552 // Set main file pointing to the file reference. 1553 file_set_sortorder($usercontext->id, 'user', 'draft', $record->files, $filerefrecord->filepath, 1554 $filerefrecord->filename, 1); 1555 1556 // Once the reference has been created, create the file resource. 1557 $resource2 = self::getDataGenerator()->create_module('resource', $record); 1558 1559 $result = core_course_external::get_course_contents($course->id); 1560 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1561 $this->assertCount(2, $result[0]['modules']); 1562 foreach ($result[0]['modules'] as $module) { 1563 if ($module['instance'] == $resource1->id) { 1564 $this->assertEquals(1, $module['contentsinfo']['filescount']); 1565 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1566 $this->assertEquals($module['contents'][0]['filesize'], $module['contentsinfo']['filessize']); 1567 $this->assertEquals(array('text/plain'), $module['contentsinfo']['mimetypes']); 1568 } else { 1569 $this->assertEquals(count($extensions) + 1, $module['contentsinfo']['filescount']); 1570 $filessize = $module['contents'][0]['filesize'] + $module['contents'][1]['filesize'] + 1571 $module['contents'][2]['filesize'] + $module['contents'][3]['filesize']; 1572 $this->assertEquals($filessize, $module['contentsinfo']['filessize']); 1573 $this->assertEquals('user', $module['contentsinfo']['repositorytype']); 1574 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1575 $this->assertEquals(array('text/plain', 'image/png', 'application/pdf'), $module['contentsinfo']['mimetypes']); 1576 } 1577 } 1578 } 1579 1580 /** 1581 * Test get_course_contents when hidden sections are displayed. 1582 */ 1583 public function test_get_course_contents_hiddensections() { 1584 global $DB; 1585 $this->resetAfterTest(true); 1586 1587 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1588 // Force returning hidden sections. 1589 $course->hiddensections = 0; 1590 update_course($course); 1591 1592 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1593 $user = self::getDataGenerator()->create_user(); 1594 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1595 $this->setUser($user); 1596 1597 $sections = core_course_external::get_course_contents($course->id, array()); 1598 // We need to execute the return values cleaning process to simulate the web service server. 1599 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1600 1601 $this->assertCount(5, $sections); // All the sections, including the "not visible" one. 1602 $this->assertCount(6, $sections[0]['modules']); 1603 $this->assertCount(1, $sections[1]['modules']); 1604 $this->assertCount(1, $sections[2]['modules']); 1605 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1606 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1607 1608 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1609 $this->assertEquals(1, $sections[1]['section']); 1610 $this->assertEquals(2, $sections[2]['section']); 1611 $this->assertEquals(3, $sections[3]['section']); 1612 // The module with the availability restriction met is returning contents. 1613 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1614 // The module with the availability restriction not met is not returning contents. 1615 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1616 1617 // Now include flag for returning stealth information (fake section). 1618 $sections = core_course_external::get_course_contents($course->id, 1619 array(array("name" => "includestealthmodules", "value" => 1))); 1620 // We need to execute the return values cleaning process to simulate the web service server. 1621 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1622 1623 $this->assertCount(6, $sections); // Include fake section with stealth activities. 1624 $this->assertCount(6, $sections[0]['modules']); 1625 $this->assertCount(1, $sections[1]['modules']); 1626 $this->assertCount(1, $sections[2]['modules']); 1627 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1628 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1629 $this->assertCount(1, $sections[5]['modules']); // One stealth module. 1630 $this->assertEquals(-1, $sections[5]['id']); 1631 } 1632 1633 /** 1634 * Test duplicate_course 1635 */ 1636 public function test_duplicate_course() { 1637 $this->resetAfterTest(true); 1638 1639 // Create one course with three modules. 1640 $course = self::getDataGenerator()->create_course(); 1641 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id)); 1642 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 1643 $forumcontext = context_module::instance($forum->cmid); 1644 $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id)); 1645 $datacontext = context_module::instance($data->cmid); 1646 $datacm = get_coursemodule_from_instance('page', $data->id); 1647 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id)); 1648 $pagecontext = context_module::instance($page->cmid); 1649 $pagecm = get_coursemodule_from_instance('page', $page->id); 1650 1651 // Set the required capabilities by the external function. 1652 $coursecontext = context_course::instance($course->id); 1653 $categorycontext = context_coursecat::instance($course->category); 1654 $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id); 1655 $this->assignUserCapability('moodle/course:view', $categorycontext->id, $roleid); 1656 $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id, $roleid); 1657 $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id, $roleid); 1658 $this->assignUserCapability('moodle/backup:configure', $coursecontext->id, $roleid); 1659 // Optional capabilities to copy user data. 1660 $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id, $roleid); 1661 $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id, $roleid); 1662 1663 $newcourse['fullname'] = 'Course duplicate'; 1664 $newcourse['shortname'] = 'courseduplicate'; 1665 $newcourse['categoryid'] = $course->category; 1666 $newcourse['visible'] = true; 1667 $newcourse['options'][] = array('name' => 'users', 'value' => true); 1668 1669 $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'], 1670 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']); 1671 1672 // We need to execute the return values cleaning process to simulate the web service server. 1673 $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate); 1674 1675 // Check that the course has been duplicated. 1676 $this->assertEquals($newcourse['shortname'], $duplicate['shortname']); 1677 } 1678 1679 /** 1680 * Test update_courses 1681 */ 1682 public function test_update_courses() { 1683 global $DB, $CFG, $USER, $COURSE; 1684 1685 // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this 1686 // trick because we are both updating and getting (for testing) course information 1687 // in the same request and core_course_external::update_courses() 1688 // is overwriting $COURSE all over the time with OLD values, so later 1689 // use of get_course() fetches those OLD values instead of the updated ones. 1690 // See MDL-39723 for more info. 1691 $origcourse = clone($COURSE); 1692 1693 $this->resetAfterTest(true); 1694 1695 // Set the required capabilities by the external function. 1696 $contextid = context_system::instance()->id; 1697 $roleid = $this->assignUserCapability('moodle/course:update', $contextid); 1698 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1699 $this->assignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 1700 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1701 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1702 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1703 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1704 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 1705 $this->assignUserCapability('moodle/course:viewhiddencourses', $contextid, $roleid); 1706 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid); 1707 1708 // Create category and courses. 1709 $category1 = self::getDataGenerator()->create_category(); 1710 $category2 = self::getDataGenerator()->create_category(); 1711 1712 $originalcourse1 = self::getDataGenerator()->create_course(); 1713 self::getDataGenerator()->enrol_user($USER->id, $originalcourse1->id, $roleid); 1714 1715 $originalcourse2 = self::getDataGenerator()->create_course(); 1716 self::getDataGenerator()->enrol_user($USER->id, $originalcourse2->id, $roleid); 1717 1718 // Course with custom fields. 1719 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 1720 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 1721 'categoryid' => $fieldcategory->get('id'), 1722 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL, 'locked' => 1]]; 1723 $field = self::getDataGenerator()->create_custom_field($customfield); 1724 1725 $originalcourse3 = self::getDataGenerator()->create_course(['customfield_test' => 'Test value']); 1726 self::getDataGenerator()->enrol_user($USER->id, $originalcourse3->id, $roleid); 1727 1728 // Course values to be updated. 1729 $course1['id'] = $originalcourse1->id; 1730 $course1['fullname'] = 'Updated test course 1'; 1731 $course1['shortname'] = 'Udestedtestcourse1'; 1732 $course1['categoryid'] = $category1->id; 1733 1734 $course2['id'] = $originalcourse2->id; 1735 $course2['fullname'] = 'Updated test course 2'; 1736 $course2['shortname'] = 'Updestedtestcourse2'; 1737 $course2['categoryid'] = $category2->id; 1738 $course2['idnumber'] = 'Updatedidnumber2'; 1739 $course2['summary'] = 'Updaated description for course 2'; 1740 $course2['summaryformat'] = FORMAT_HTML; 1741 $course2['format'] = 'topics'; 1742 $course2['showgrades'] = 1; 1743 $course2['newsitems'] = 3; 1744 $course2['startdate'] = 1420092000; // 01/01/2015. 1745 $course2['enddate'] = 1422669600; // 01/31/2015. 1746 $course2['maxbytes'] = 100000; 1747 $course2['showreports'] = 1; 1748 $course2['visible'] = 0; 1749 $course2['hiddensections'] = 0; 1750 $course2['groupmode'] = 0; 1751 $course2['groupmodeforce'] = 0; 1752 $course2['defaultgroupingid'] = 0; 1753 $course2['enablecompletion'] = 1; 1754 $course2['lang'] = 'en'; 1755 $course2['forcetheme'] = 'classic'; 1756 1757 $course3['id'] = $originalcourse3->id; 1758 $updatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'Updated test value']; 1759 $course3['customfields'] = [$updatedcustomfieldvalue]; 1760 $courses = array($course1, $course2, $course3); 1761 1762 $updatedcoursewarnings = core_course_external::update_courses($courses); 1763 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1764 $updatedcoursewarnings); 1765 $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line. 1766 1767 // Check that right number of courses were created. 1768 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1769 1770 // Check that the courses were correctly created. 1771 foreach ($courses as $course) { 1772 $courseinfo = course_get_format($course['id'])->get_course(); 1773 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course['id']); 1774 if ($course['id'] == $course2['id']) { 1775 $this->assertEquals($course2['fullname'], $courseinfo->fullname); 1776 $this->assertEquals($course2['shortname'], $courseinfo->shortname); 1777 $this->assertEquals($course2['categoryid'], $courseinfo->category); 1778 $this->assertEquals($course2['idnumber'], $courseinfo->idnumber); 1779 $this->assertEquals($course2['summary'], $courseinfo->summary); 1780 $this->assertEquals($course2['summaryformat'], $courseinfo->summaryformat); 1781 $this->assertEquals($course2['format'], $courseinfo->format); 1782 $this->assertEquals($course2['showgrades'], $courseinfo->showgrades); 1783 $this->assertEquals($course2['newsitems'], $courseinfo->newsitems); 1784 $this->assertEquals($course2['startdate'], $courseinfo->startdate); 1785 $this->assertEquals($course2['enddate'], $courseinfo->enddate); 1786 $this->assertEquals($course2['maxbytes'], $courseinfo->maxbytes); 1787 $this->assertEquals($course2['showreports'], $courseinfo->showreports); 1788 $this->assertEquals($course2['visible'], $courseinfo->visible); 1789 $this->assertEquals($course2['hiddensections'], $courseinfo->hiddensections); 1790 $this->assertEquals($course2['groupmode'], $courseinfo->groupmode); 1791 $this->assertEquals($course2['groupmodeforce'], $courseinfo->groupmodeforce); 1792 $this->assertEquals($course2['defaultgroupingid'], $courseinfo->defaultgroupingid); 1793 $this->assertEquals($course2['lang'], $courseinfo->lang); 1794 1795 if (!empty($CFG->allowcoursethemes)) { 1796 $this->assertEquals($course2['forcetheme'], $courseinfo->theme); 1797 } 1798 1799 $this->assertEquals($course2['enablecompletion'], $courseinfo->enablecompletion); 1800 $this->assertEquals(['test' => null], (array)$customfields); 1801 } else if ($course['id'] == $course1['id']) { 1802 $this->assertEquals($course1['fullname'], $courseinfo->fullname); 1803 $this->assertEquals($course1['shortname'], $courseinfo->shortname); 1804 $this->assertEquals($course1['categoryid'], $courseinfo->category); 1805 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1806 $this->assertEquals('topics', $courseinfo->format); 1807 $this->assertEquals(5, course_get_format($course['id'])->get_last_section_number()); 1808 $this->assertEquals(0, $courseinfo->newsitems); 1809 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1810 $this->assertEquals(['test' => null], (array)$customfields); 1811 } else if ($course['id'] == $course3['id']) { 1812 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 1813 } else { 1814 throw new moodle_exception('Unexpected shortname'); 1815 } 1816 } 1817 1818 $courses = array($course1); 1819 // Try update course without update capability. 1820 $user = self::getDataGenerator()->create_user(); 1821 $this->setUser($user); 1822 $this->unassignUserCapability('moodle/course:update', $contextid, $roleid); 1823 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1824 $updatedcoursewarnings = core_course_external::update_courses($courses); 1825 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1826 $updatedcoursewarnings); 1827 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1828 1829 // Try update course category without capability. 1830 $this->assignUserCapability('moodle/course:update', $contextid, $roleid); 1831 $this->unassignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1832 $user = self::getDataGenerator()->create_user(); 1833 $this->setUser($user); 1834 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1835 $course1['categoryid'] = $category2->id; 1836 $courses = array($course1); 1837 $updatedcoursewarnings = core_course_external::update_courses($courses); 1838 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1839 $updatedcoursewarnings); 1840 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1841 1842 // Try update course fullname without capability. 1843 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1844 $this->unassignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1845 $user = self::getDataGenerator()->create_user(); 1846 $this->setUser($user); 1847 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1848 $updatedcoursewarnings = core_course_external::update_courses($courses); 1849 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1850 $updatedcoursewarnings); 1851 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1852 $course1['fullname'] = 'Testing fullname without permission'; 1853 $courses = array($course1); 1854 $updatedcoursewarnings = core_course_external::update_courses($courses); 1855 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1856 $updatedcoursewarnings); 1857 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1858 1859 // Try update course shortname without capability. 1860 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1861 $this->unassignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1862 $user = self::getDataGenerator()->create_user(); 1863 $this->setUser($user); 1864 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1865 $updatedcoursewarnings = core_course_external::update_courses($courses); 1866 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1867 $updatedcoursewarnings); 1868 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1869 $course1['shortname'] = 'Testing shortname without permission'; 1870 $courses = array($course1); 1871 $updatedcoursewarnings = core_course_external::update_courses($courses); 1872 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1873 $updatedcoursewarnings); 1874 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1875 1876 // Try update course idnumber without capability. 1877 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1878 $this->unassignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1879 $user = self::getDataGenerator()->create_user(); 1880 $this->setUser($user); 1881 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1882 $updatedcoursewarnings = core_course_external::update_courses($courses); 1883 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1884 $updatedcoursewarnings); 1885 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1886 $course1['idnumber'] = 'NEWIDNUMBER'; 1887 $courses = array($course1); 1888 $updatedcoursewarnings = core_course_external::update_courses($courses); 1889 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1890 $updatedcoursewarnings); 1891 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1892 1893 // Try update course summary without capability. 1894 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1895 $this->unassignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1896 $user = self::getDataGenerator()->create_user(); 1897 $this->setUser($user); 1898 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1899 $updatedcoursewarnings = core_course_external::update_courses($courses); 1900 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1901 $updatedcoursewarnings); 1902 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1903 $course1['summary'] = 'New summary'; 1904 $courses = array($course1); 1905 $updatedcoursewarnings = core_course_external::update_courses($courses); 1906 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1907 $updatedcoursewarnings); 1908 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1909 1910 // Try update course with invalid summary format. 1911 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1912 $user = self::getDataGenerator()->create_user(); 1913 $this->setUser($user); 1914 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1915 $updatedcoursewarnings = core_course_external::update_courses($courses); 1916 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1917 $updatedcoursewarnings); 1918 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1919 $course1['summaryformat'] = 10; 1920 $courses = array($course1); 1921 $updatedcoursewarnings = core_course_external::update_courses($courses); 1922 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1923 $updatedcoursewarnings); 1924 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1925 1926 // Try update course visibility without capability. 1927 $this->unassignUserCapability('moodle/course:visibility', $contextid, $roleid); 1928 $user = self::getDataGenerator()->create_user(); 1929 $this->setUser($user); 1930 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1931 $course1['summaryformat'] = FORMAT_MOODLE; 1932 $courses = array($course1); 1933 $updatedcoursewarnings = core_course_external::update_courses($courses); 1934 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1935 $updatedcoursewarnings); 1936 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1937 $course1['visible'] = 0; 1938 $courses = array($course1); 1939 $updatedcoursewarnings = core_course_external::update_courses($courses); 1940 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1941 $updatedcoursewarnings); 1942 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1943 1944 // Try update course custom fields without capability. 1945 $this->unassignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 1946 $user = self::getDataGenerator()->create_user(); 1947 $this->setUser($user); 1948 self::getDataGenerator()->enrol_user($user->id, $course3['id'], $roleid); 1949 1950 $newupdatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'New updated value']; 1951 $course3['customfields'] = [$newupdatedcustomfieldvalue]; 1952 1953 core_course_external::update_courses([$course3]); 1954 1955 // Custom field was not updated. 1956 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course3['id']); 1957 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 1958 } 1959 1960 /** 1961 * Test delete course_module. 1962 */ 1963 public function test_delete_modules() { 1964 global $DB; 1965 1966 // Ensure we reset the data after this test. 1967 $this->resetAfterTest(true); 1968 1969 // Create a user. 1970 $user = self::getDataGenerator()->create_user(); 1971 1972 // Set the tests to run as the user. 1973 self::setUser($user); 1974 1975 // Create a course to add the modules. 1976 $course = self::getDataGenerator()->create_course(); 1977 1978 // Create two test modules. 1979 $record = new stdClass(); 1980 $record->course = $course->id; 1981 $module1 = self::getDataGenerator()->create_module('forum', $record); 1982 $module2 = self::getDataGenerator()->create_module('assign', $record); 1983 1984 // Check the forum was correctly created. 1985 $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id))); 1986 1987 // Check the assignment was correctly created. 1988 $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id))); 1989 1990 // Check data exists in the course modules table. 1991 $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 1992 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 1993 1994 // Enrol the user in the course. 1995 $enrol = enrol_get_plugin('manual'); 1996 $enrolinstances = enrol_get_instances($course->id, true); 1997 foreach ($enrolinstances as $courseenrolinstance) { 1998 if ($courseenrolinstance->enrol == "manual") { 1999 $instance = $courseenrolinstance; 2000 break; 2001 } 2002 } 2003 $enrol->enrol_user($instance, $user->id); 2004 2005 // Assign capabilities to delete module 1. 2006 $modcontext = context_module::instance($module1->cmid); 2007 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id); 2008 2009 // Assign capabilities to delete module 2. 2010 $modcontext = context_module::instance($module2->cmid); 2011 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 2012 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole); 2013 2014 // Deleting these module instances. 2015 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2016 2017 // Check the forum was deleted. 2018 $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id))); 2019 2020 // Check the assignment was deleted. 2021 $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id))); 2022 2023 // Check we retrieve no data in the course modules table. 2024 $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 2025 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 2026 2027 // Call with non-existent course module id and ensure exception thrown. 2028 try { 2029 core_course_external::delete_modules(array('1337')); 2030 $this->fail('Exception expected due to missing course module.'); 2031 } catch (dml_missing_record_exception $e) { 2032 $this->assertEquals('invalidcoursemodule', $e->errorcode); 2033 } 2034 2035 // Create two modules. 2036 $module1 = self::getDataGenerator()->create_module('forum', $record); 2037 $module2 = self::getDataGenerator()->create_module('assign', $record); 2038 2039 // Since these modules were recreated the user will not have capabilities 2040 // to delete them, ensure exception is thrown if they try. 2041 try { 2042 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2043 $this->fail('Exception expected due to missing capability.'); 2044 } catch (moodle_exception $e) { 2045 $this->assertEquals('nopermissions', $e->errorcode); 2046 } 2047 2048 // Unenrol user from the course. 2049 $enrol->unenrol_user($instance, $user->id); 2050 2051 // Try and delete modules from the course the user was unenrolled in, make sure exception thrown. 2052 try { 2053 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 2054 $this->fail('Exception expected due to being unenrolled from the course.'); 2055 } catch (moodle_exception $e) { 2056 $this->assertEquals('requireloginerror', $e->errorcode); 2057 } 2058 } 2059 2060 /** 2061 * Test import_course into an empty course 2062 */ 2063 public function test_import_course_empty() { 2064 global $USER; 2065 2066 $this->resetAfterTest(true); 2067 2068 $course1 = self::getDataGenerator()->create_course(); 2069 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id, 'name' => 'Forum test')); 2070 $page = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 'name' => 'Page test')); 2071 2072 $course2 = self::getDataGenerator()->create_course(); 2073 2074 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2075 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2076 2077 // Verify the state of the courses before we do the import. 2078 $this->assertCount(2, $course1cms); 2079 $this->assertEmpty($course2cms); 2080 2081 // Setup the user to run the operation (ugly hack because validate_context() will 2082 // fail as the email is not set by $this->setAdminUser()). 2083 $this->setAdminUser(); 2084 $USER->email = 'emailtopass@example.com'; 2085 2086 // Import from course1 to course2. 2087 core_course_external::import_course($course1->id, $course2->id, 0); 2088 2089 // Verify that now we have two modules in both courses. 2090 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2091 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2092 $this->assertCount(2, $course1cms); 2093 $this->assertCount(2, $course2cms); 2094 2095 // Verify that the names transfered across correctly. 2096 foreach ($course2cms as $cm) { 2097 if ($cm->modname === 'page') { 2098 $this->assertEquals($cm->name, $page->name); 2099 } else if ($cm->modname === 'forum') { 2100 $this->assertEquals($cm->name, $forum->name); 2101 } else { 2102 $this->fail('Unknown CM found.'); 2103 } 2104 } 2105 } 2106 2107 /** 2108 * Test import_course into an filled course 2109 */ 2110 public function test_import_course_filled() { 2111 global $USER; 2112 2113 $this->resetAfterTest(true); 2114 2115 // Add forum and page to course1. 2116 $course1 = self::getDataGenerator()->create_course(); 2117 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2118 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2119 2120 // Add quiz to course 2. 2121 $course2 = self::getDataGenerator()->create_course(); 2122 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2123 2124 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2125 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2126 2127 // Verify the state of the courses before we do the import. 2128 $this->assertCount(2, $course1cms); 2129 $this->assertCount(1, $course2cms); 2130 2131 // Setup the user to run the operation (ugly hack because validate_context() will 2132 // fail as the email is not set by $this->setAdminUser()). 2133 $this->setAdminUser(); 2134 $USER->email = 'emailtopass@example.com'; 2135 2136 // Import from course1 to course2 without deleting content. 2137 core_course_external::import_course($course1->id, $course2->id, 0); 2138 2139 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2140 2141 // Verify that now we have three modules in course2. 2142 $this->assertCount(3, $course2cms); 2143 2144 // Verify that the names transfered across correctly. 2145 foreach ($course2cms as $cm) { 2146 if ($cm->modname === 'page') { 2147 $this->assertEquals($cm->name, $page->name); 2148 } else if ($cm->modname === 'forum') { 2149 $this->assertEquals($cm->name, $forum->name); 2150 } else if ($cm->modname === 'quiz') { 2151 $this->assertEquals($cm->name, $quiz->name); 2152 } else { 2153 $this->fail('Unknown CM found.'); 2154 } 2155 } 2156 } 2157 2158 /** 2159 * Test import_course with only blocks set to backup 2160 */ 2161 public function test_import_course_blocksonly() { 2162 global $USER, $DB; 2163 2164 $this->resetAfterTest(true); 2165 2166 // Add forum and page to course1. 2167 $course1 = self::getDataGenerator()->create_course(); 2168 $course1ctx = context_course::instance($course1->id); 2169 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2170 $block = $this->getDataGenerator()->create_block('online_users', array('parentcontextid' => $course1ctx->id)); 2171 2172 $course2 = self::getDataGenerator()->create_course(); 2173 $course2ctx = context_course::instance($course2->id); 2174 $initialblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2175 $initialcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2176 2177 // Setup the user to run the operation (ugly hack because validate_context() will 2178 // fail as the email is not set by $this->setAdminUser()). 2179 $this->setAdminUser(); 2180 $USER->email = 'emailtopass@example.com'; 2181 2182 // Import from course1 to course2 without deleting content, but excluding 2183 // activities. 2184 $options = array( 2185 array('name' => 'activities', 'value' => 0), 2186 array('name' => 'blocks', 'value' => 1), 2187 array('name' => 'filters', 'value' => 0), 2188 ); 2189 2190 core_course_external::import_course($course1->id, $course2->id, 0, $options); 2191 2192 $newcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2193 $newblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2194 // Check that course modules haven't changed, but that blocks have. 2195 $this->assertEquals($initialcmcount, $newcmcount); 2196 $this->assertEquals(($initialblockcount + 1), $newblockcount); 2197 } 2198 2199 /** 2200 * Test import_course into an filled course, deleting content. 2201 */ 2202 public function test_import_course_deletecontent() { 2203 global $USER; 2204 $this->resetAfterTest(true); 2205 2206 // Add forum and page to course1. 2207 $course1 = self::getDataGenerator()->create_course(); 2208 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2209 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2210 2211 // Add quiz to course 2. 2212 $course2 = self::getDataGenerator()->create_course(); 2213 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2214 2215 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2216 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2217 2218 // Verify the state of the courses before we do the import. 2219 $this->assertCount(2, $course1cms); 2220 $this->assertCount(1, $course2cms); 2221 2222 // Setup the user to run the operation (ugly hack because validate_context() will 2223 // fail as the email is not set by $this->setAdminUser()). 2224 $this->setAdminUser(); 2225 $USER->email = 'emailtopass@example.com'; 2226 2227 // Import from course1 to course2, deleting content. 2228 core_course_external::import_course($course1->id, $course2->id, 1); 2229 2230 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2231 2232 // Verify that now we have two modules in course2. 2233 $this->assertCount(2, $course2cms); 2234 2235 // Verify that the course only contains the imported modules. 2236 foreach ($course2cms as $cm) { 2237 if ($cm->modname === 'page') { 2238 $this->assertEquals($cm->name, $page->name); 2239 } else if ($cm->modname === 'forum') { 2240 $this->assertEquals($cm->name, $forum->name); 2241 } else { 2242 $this->fail('Unknown CM found: '.$cm->name); 2243 } 2244 } 2245 } 2246 2247 /** 2248 * Ensure import_course handles incorrect deletecontent option correctly. 2249 */ 2250 public function test_import_course_invalid_deletecontent_option() { 2251 $this->resetAfterTest(true); 2252 2253 $course1 = self::getDataGenerator()->create_course(); 2254 $course2 = self::getDataGenerator()->create_course(); 2255 2256 $this->expectException('moodle_exception'); 2257 $this->expectExceptionMessage(get_string('invalidextparam', 'webservice', -1)); 2258 // Import from course1 to course2, with invalid option 2259 core_course_external::import_course($course1->id, $course2->id, -1);; 2260 } 2261 2262 /** 2263 * Test view_course function 2264 */ 2265 public function test_view_course() { 2266 2267 $this->resetAfterTest(); 2268 2269 // Course without sections. 2270 $course = $this->getDataGenerator()->create_course(array('numsections' => 5), array('createsections' => true)); 2271 $this->setAdminUser(); 2272 2273 // Redirect events to the sink, so we can recover them later. 2274 $sink = $this->redirectEvents(); 2275 2276 $result = core_course_external::view_course($course->id, 1); 2277 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2278 $events = $sink->get_events(); 2279 $event = reset($events); 2280 2281 // Check the event details are correct. 2282 $this->assertInstanceOf('\core\event\course_viewed', $event); 2283 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2284 $this->assertEquals(1, $event->other['coursesectionnumber']); 2285 2286 $result = core_course_external::view_course($course->id); 2287 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2288 $events = $sink->get_events(); 2289 $event = array_pop($events); 2290 $sink->close(); 2291 2292 // Check the event details are correct. 2293 $this->assertInstanceOf('\core\event\course_viewed', $event); 2294 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2295 $this->assertEmpty($event->other); 2296 2297 } 2298 2299 /** 2300 * Test get_course_module 2301 */ 2302 public function test_get_course_module() { 2303 global $DB; 2304 2305 $this->resetAfterTest(true); 2306 2307 $this->setAdminUser(); 2308 $course = self::getDataGenerator()->create_course(); 2309 $record = array( 2310 'course' => $course->id, 2311 'name' => 'First Assignment' 2312 ); 2313 $options = array( 2314 'idnumber' => 'ABC', 2315 'visible' => 0 2316 ); 2317 // Hidden activity. 2318 $assign = self::getDataGenerator()->create_module('assign', $record, $options); 2319 2320 $outcomescale = 'Distinction, Very Good, Good, Pass, Fail'; 2321 2322 // Insert a custom grade scale to be used by an outcome. 2323 $gradescale = new grade_scale(); 2324 $gradescale->name = 'gettcoursemodulescale'; 2325 $gradescale->courseid = $course->id; 2326 $gradescale->userid = 0; 2327 $gradescale->scale = $outcomescale; 2328 $gradescale->description = 'This scale is used to mark standard assignments.'; 2329 $gradescale->insert(); 2330 2331 // Insert an outcome. 2332 $data = new stdClass(); 2333 $data->courseid = $course->id; 2334 $data->fullname = 'Team work'; 2335 $data->shortname = 'Team work'; 2336 $data->scaleid = $gradescale->id; 2337 $outcome = new grade_outcome($data, false); 2338 $outcome->insert(); 2339 2340 $outcomegradeitem = new grade_item(); 2341 $outcomegradeitem->itemname = $outcome->shortname; 2342 $outcomegradeitem->itemtype = 'mod'; 2343 $outcomegradeitem->itemmodule = 'assign'; 2344 $outcomegradeitem->iteminstance = $assign->id; 2345 $outcomegradeitem->outcomeid = $outcome->id; 2346 $outcomegradeitem->cmid = 0; 2347 $outcomegradeitem->courseid = $course->id; 2348 $outcomegradeitem->aggregationcoef = 0; 2349 $outcomegradeitem->itemnumber = 1000; // Outcomes start at 1000. 2350 $outcomegradeitem->gradetype = GRADE_TYPE_SCALE; 2351 $outcomegradeitem->scaleid = $outcome->scaleid; 2352 $outcomegradeitem->insert(); 2353 2354 $assignmentgradeitem = grade_item::fetch( 2355 array( 2356 'itemtype' => 'mod', 2357 'itemmodule' => 'assign', 2358 'iteminstance' => $assign->id, 2359 'itemnumber' => 0, 2360 'courseid' => $course->id 2361 ) 2362 ); 2363 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid); 2364 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder); 2365 2366 // Test admin user can see the complete hidden activity. 2367 $result = core_course_external::get_course_module($assign->cmid); 2368 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2369 2370 $this->assertCount(0, $result['warnings']); 2371 // Test we retrieve all the fields. 2372 $this->assertCount(28, $result['cm']); 2373 $this->assertEquals($record['name'], $result['cm']['name']); 2374 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2375 $this->assertEquals(100, $result['cm']['grade']); 2376 $this->assertEquals(0.0, $result['cm']['gradepass']); 2377 $this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']); 2378 $this->assertEmpty($result['cm']['advancedgrading'][0]['method']); 2379 $this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']); 2380 2381 $student = $this->getDataGenerator()->create_user(); 2382 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2383 2384 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2385 $this->setUser($student); 2386 2387 // The user shouldn't be able to see the activity. 2388 try { 2389 core_course_external::get_course_module($assign->cmid); 2390 $this->fail('Exception expected due to invalid permissions.'); 2391 } catch (moodle_exception $e) { 2392 $this->assertEquals('requireloginerror', $e->errorcode); 2393 } 2394 2395 // Make module visible. 2396 set_coursemodule_visible($assign->cmid, 1); 2397 2398 // Test student user. 2399 $result = core_course_external::get_course_module($assign->cmid); 2400 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2401 2402 $this->assertCount(0, $result['warnings']); 2403 // Test we retrieve only the few files we can see. 2404 $this->assertCount(11, $result['cm']); 2405 $this->assertEquals($assign->cmid, $result['cm']['id']); 2406 $this->assertEquals($course->id, $result['cm']['course']); 2407 $this->assertEquals('assign', $result['cm']['modname']); 2408 $this->assertEquals($assign->id, $result['cm']['instance']); 2409 2410 } 2411 2412 /** 2413 * Test get_course_module_by_instance 2414 */ 2415 public function test_get_course_module_by_instance() { 2416 global $DB; 2417 2418 $this->resetAfterTest(true); 2419 2420 $this->setAdminUser(); 2421 $course = self::getDataGenerator()->create_course(); 2422 $record = array( 2423 'course' => $course->id, 2424 'name' => 'First quiz', 2425 'grade' => 90.00 2426 ); 2427 $options = array( 2428 'idnumber' => 'ABC', 2429 'visible' => 0 2430 ); 2431 // Hidden activity. 2432 $quiz = self::getDataGenerator()->create_module('quiz', $record, $options); 2433 2434 // Test admin user can see the complete hidden activity. 2435 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2436 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2437 2438 $this->assertCount(0, $result['warnings']); 2439 // Test we retrieve all the fields. 2440 $this->assertCount(26, $result['cm']); 2441 $this->assertEquals($record['name'], $result['cm']['name']); 2442 $this->assertEquals($record['grade'], $result['cm']['grade']); 2443 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2444 2445 $student = $this->getDataGenerator()->create_user(); 2446 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2447 2448 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2449 $this->setUser($student); 2450 2451 // The user shouldn't be able to see the activity. 2452 try { 2453 core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2454 $this->fail('Exception expected due to invalid permissions.'); 2455 } catch (moodle_exception $e) { 2456 $this->assertEquals('requireloginerror', $e->errorcode); 2457 } 2458 2459 // Make module visible. 2460 set_coursemodule_visible($quiz->cmid, 1); 2461 2462 // Test student user. 2463 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2464 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2465 2466 $this->assertCount(0, $result['warnings']); 2467 // Test we retrieve only the few files we can see. 2468 $this->assertCount(11, $result['cm']); 2469 $this->assertEquals($quiz->cmid, $result['cm']['id']); 2470 $this->assertEquals($course->id, $result['cm']['course']); 2471 $this->assertEquals('quiz', $result['cm']['modname']); 2472 $this->assertEquals($quiz->id, $result['cm']['instance']); 2473 2474 // Try with an invalid module name. 2475 try { 2476 core_course_external::get_course_module_by_instance('abc', $quiz->id); 2477 $this->fail('Exception expected due to invalid module name.'); 2478 } catch (dml_read_exception $e) { 2479 $this->assertEquals('dmlreadexception', $e->errorcode); 2480 } 2481 2482 } 2483 2484 /** 2485 * Test get_user_navigation_options 2486 */ 2487 public function test_get_user_navigation_options() { 2488 global $USER; 2489 2490 $this->resetAfterTest(); 2491 $course1 = self::getDataGenerator()->create_course(); 2492 $course2 = self::getDataGenerator()->create_course(); 2493 2494 // Create a viewer user. 2495 $viewer = self::getDataGenerator()->create_user(); 2496 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2497 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2498 2499 $this->setUser($viewer->id); 2500 $courses = array($course1->id , $course2->id, SITEID); 2501 2502 $result = core_course_external::get_user_navigation_options($courses); 2503 $result = external_api::clean_returnvalue(core_course_external::get_user_navigation_options_returns(), $result); 2504 2505 $this->assertCount(0, $result['warnings']); 2506 $this->assertCount(3, $result['courses']); 2507 2508 foreach ($result['courses'] as $course) { 2509 $navoptions = new stdClass; 2510 foreach ($course['options'] as $option) { 2511 $navoptions->{$option['name']} = $option['available']; 2512 } 2513 $this->assertCount(9, $course['options']); 2514 if ($course['id'] == SITEID) { 2515 $this->assertTrue($navoptions->blogs); 2516 $this->assertFalse($navoptions->notes); 2517 $this->assertFalse($navoptions->participants); 2518 $this->assertTrue($navoptions->badges); 2519 $this->assertTrue($navoptions->tags); 2520 $this->assertFalse($navoptions->grades); 2521 $this->assertFalse($navoptions->search); 2522 $this->assertTrue($navoptions->calendar); 2523 $this->assertTrue($navoptions->competencies); 2524 } else { 2525 $this->assertTrue($navoptions->blogs); 2526 $this->assertFalse($navoptions->notes); 2527 $this->assertTrue($navoptions->participants); 2528 $this->assertFalse($navoptions->badges); 2529 $this->assertFalse($navoptions->tags); 2530 $this->assertTrue($navoptions->grades); 2531 $this->assertFalse($navoptions->search); 2532 $this->assertFalse($navoptions->calendar); 2533 $this->assertTrue($navoptions->competencies); 2534 } 2535 } 2536 } 2537 2538 /** 2539 * Test get_user_administration_options 2540 */ 2541 public function test_get_user_administration_options() { 2542 global $USER; 2543 2544 $this->resetAfterTest(); 2545 $course1 = self::getDataGenerator()->create_course(); 2546 $course2 = self::getDataGenerator()->create_course(); 2547 2548 // Create a viewer user. 2549 $viewer = self::getDataGenerator()->create_user(); 2550 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2551 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2552 2553 $this->setUser($viewer->id); 2554 $courses = array($course1->id , $course2->id, SITEID); 2555 2556 $result = core_course_external::get_user_administration_options($courses); 2557 $result = external_api::clean_returnvalue(core_course_external::get_user_administration_options_returns(), $result); 2558 2559 $this->assertCount(0, $result['warnings']); 2560 $this->assertCount(3, $result['courses']); 2561 2562 foreach ($result['courses'] as $course) { 2563 $adminoptions = new stdClass; 2564 foreach ($course['options'] as $option) { 2565 $adminoptions->{$option['name']} = $option['available']; 2566 } 2567 if ($course['id'] == SITEID) { 2568 $this->assertCount(17, $course['options']); 2569 $this->assertFalse($adminoptions->update); 2570 $this->assertFalse($adminoptions->filters); 2571 $this->assertFalse($adminoptions->reports); 2572 $this->assertFalse($adminoptions->backup); 2573 $this->assertFalse($adminoptions->restore); 2574 $this->assertFalse($adminoptions->files); 2575 $this->assertFalse(!isset($adminoptions->tags)); 2576 $this->assertFalse($adminoptions->gradebook); 2577 $this->assertFalse($adminoptions->outcomes); 2578 $this->assertFalse($adminoptions->badges); 2579 $this->assertFalse($adminoptions->import); 2580 $this->assertFalse($adminoptions->reset); 2581 $this->assertFalse($adminoptions->roles); 2582 $this->assertFalse($adminoptions->editcompletion); 2583 $this->assertFalse($adminoptions->copy); 2584 } else { 2585 $this->assertCount(15, $course['options']); 2586 $this->assertFalse($adminoptions->update); 2587 $this->assertFalse($adminoptions->filters); 2588 $this->assertFalse($adminoptions->reports); 2589 $this->assertFalse($adminoptions->backup); 2590 $this->assertFalse($adminoptions->restore); 2591 $this->assertFalse($adminoptions->files); 2592 $this->assertFalse($adminoptions->tags); 2593 $this->assertFalse($adminoptions->gradebook); 2594 $this->assertFalse($adminoptions->outcomes); 2595 $this->assertTrue($adminoptions->badges); 2596 $this->assertFalse($adminoptions->import); 2597 $this->assertFalse($adminoptions->reset); 2598 $this->assertFalse($adminoptions->roles); 2599 $this->assertFalse($adminoptions->editcompletion); 2600 $this->assertFalse($adminoptions->copy); 2601 } 2602 } 2603 } 2604 2605 /** 2606 * Test get_courses_by_fields 2607 */ 2608 public function test_get_courses_by_field() { 2609 global $DB; 2610 $this->resetAfterTest(true); 2611 2612 $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1')); 2613 $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id)); 2614 $course1 = self::getDataGenerator()->create_course( 2615 array('category' => $category1->id, 'shortname' => 'c1', 'format' => 'topics')); 2616 2617 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 2618