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