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 $this->assertFalse($adminoptions->tags); 2631 $this->assertFalse($adminoptions->gradebook); 2632 $this->assertFalse($adminoptions->outcomes); 2633 $this->assertTrue($adminoptions->badges); 2634 $this->assertFalse($adminoptions->import); 2635 $this->assertFalse($adminoptions->reset); 2636 $this->assertFalse($adminoptions->roles); 2637 $this->assertFalse($adminoptions->editcompletion); 2638 $this->assertFalse($adminoptions->copy); 2639 } 2640 } 2641 } 2642 2643 /** 2644 * Test get_courses_by_fields 2645 */ 2646 public function test_get_courses_by_field() { 2647 global $DB; 2648 $this->resetAfterTest(true); 2649 2650 $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1')); 2651 $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id)); 2652 $course1 = self::getDataGenerator()->create_course( 2653 array('category' => $category1->id, 'shortname' => 'c1', 'format' => 'topics')); 2654 2655 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 2656 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 2657 'categoryid' => $fieldcategory->get('id')]; 2658 $field = self::getDataGenerator()->create_custom_field($customfield); 2659 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; 2660 $course2 = self::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id, 'idnumber' => 'i2', 'customfields' => [$customfieldvalue])); 2661 2662 $student1 = self::getDataGenerator()->create_user(); 2663 $user1 = self::getDataGenerator()->create_user(); 2664 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2665 self::getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id); 2666 self::getDataGenerator()->enrol_user($student1->id, $course2->id, $studentrole->id); 2667 2668 self::setAdminUser(); 2669 // As admins, we should be able to retrieve everything. 2670 $result = core_course_external::get_courses_by_field(); 2671 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2672 $this->assertCount(3, $result['courses']); 2673 // Expect to receive all the fields. 2674 $this->assertCount(40, $result['courses'][0]); 2675 $this->assertCount(41, $result['courses'][1]); // One more field because is not the site course. 2676 $this->assertCount(41, $result['courses'][2]); // One more field because is not the site course. 2677 2678 $result = core_course_external::get_courses_by_field('id', $course1->id); 2679 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2680 $this->assertCount(1, $result['courses']); 2681 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2682 // Expect to receive all the fields. 2683 $this->assertCount(41, $result['courses'][0]); 2684 // Check default values for course format topics. 2685 $this->assertCount(2, $result['courses'][0]['courseformatoptions']); 2686 foreach ($result['courses'][0]['courseformatoptions'] as $option) { 2687 if ($option['name'] == 'hiddensections') { 2688 $this->assertEquals(0, $option['value']); 2689 } else { 2690 $this->assertEquals('coursedisplay', $option['name']); 2691 $this->assertEquals(0, $option['value']); 2692 } 2693 } 2694 2695 $result = core_course_external::get_courses_by_field('id', $course2->id); 2696 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2697 $this->assertCount(1, $result['courses']); 2698 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2699 // Check custom fields properly returned. 2700 $this->assertEquals([ 2701 'shortname' => $customfield['shortname'], 2702 'name' => $customfield['name'], 2703 'type' => $customfield['type'], 2704 'value' => $customfieldvalue['value'], 2705 'valueraw' => $customfieldvalue['value'], 2706 ], $result['courses'][0]['customfields'][0]); 2707 2708 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2709 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2710 $this->assertCount(2, $result['courses']); 2711 2712 // Check default filters. 2713 $this->assertCount(6, $result['courses'][0]['filters']); 2714 $this->assertCount(6, $result['courses'][1]['filters']); 2715 2716 $result = core_course_external::get_courses_by_field('category', $category1->id); 2717 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2718 $this->assertCount(1, $result['courses']); 2719 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2720 $this->assertEquals('Cat 1', $result['courses'][0]['categoryname']); 2721 2722 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2723 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2724 $this->assertCount(1, $result['courses']); 2725 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2726 2727 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2728 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2729 $this->assertCount(1, $result['courses']); 2730 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2731 2732 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2733 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2734 $this->assertCount(0, $result['courses']); 2735 2736 // Change filter value. 2737 filter_set_local_state('mediaplugin', context_course::instance($course1->id)->id, TEXTFILTER_OFF); 2738 2739 self::setUser($student1); 2740 // All visible courses (including front page) for normal student. 2741 $result = core_course_external::get_courses_by_field(); 2742 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2743 $this->assertCount(2, $result['courses']); 2744 $this->assertCount(33, $result['courses'][0]); 2745 $this->assertCount(34, $result['courses'][1]); // One field more (course format options), not present in site course. 2746 2747 $result = core_course_external::get_courses_by_field('id', $course1->id); 2748 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2749 $this->assertCount(1, $result['courses']); 2750 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2751 // Expect to receive all the files that a student can see. 2752 $this->assertCount(34, $result['courses'][0]); 2753 2754 // Check default filters. 2755 $filters = $result['courses'][0]['filters']; 2756 $this->assertCount(6, $filters); 2757 $found = false; 2758 foreach ($filters as $filter) { 2759 if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF) { 2760 $found = true; 2761 } 2762 } 2763 $this->assertTrue($found); 2764 2765 // Course 2 is not visible. 2766 $result = core_course_external::get_courses_by_field('id', $course2->id); 2767 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2768 $this->assertCount(0, $result['courses']); 2769 2770 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2771 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2772 $this->assertCount(1, $result['courses']); 2773 2774 $result = core_course_external::get_courses_by_field('category', $category1->id); 2775 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2776 $this->assertCount(1, $result['courses']); 2777 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2778 2779 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2780 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2781 $this->assertCount(1, $result['courses']); 2782 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2783 2784 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2785 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2786 $this->assertCount(0, $result['courses']); 2787 2788 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2789 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2790 $this->assertCount(0, $result['courses']); 2791 2792 self::setUser($user1); 2793 // All visible courses (including front page) for authenticated user. 2794 $result = core_course_external::get_courses_by_field(); 2795 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2796 $this->assertCount(2, $result['courses']); 2797 $this->assertCount(33, $result['courses'][0]); // Site course. 2798 $this->assertCount(16, $result['courses'][1]); // Only public information, not enrolled. 2799 2800 $result = core_course_external::get_courses_by_field('id', $course1->id); 2801 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2802 $this->assertCount(1, $result['courses']); 2803 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2804 // Expect to receive all the files that a authenticated can see. 2805 $this->assertCount(16, $result['courses'][0]); 2806 2807 // Course 2 is not visible. 2808 $result = core_course_external::get_courses_by_field('id', $course2->id); 2809 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2810 $this->assertCount(0, $result['courses']); 2811 2812 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2813 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2814 $this->assertCount(1, $result['courses']); 2815 2816 $result = core_course_external::get_courses_by_field('category', $category1->id); 2817 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2818 $this->assertCount(1, $result['courses']); 2819 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2820 2821 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2822 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2823 $this->assertCount(1, $result['courses']); 2824 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2825 2826 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2827 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2828 $this->assertCount(0, $result['courses']); 2829 2830 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2831 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2832 $this->assertCount(0, $result['courses']); 2833 } 2834 2835 /** 2836 * Test retrieving courses by field returns custom field data 2837 */ 2838 public function test_get_courses_by_field_customfields(): void { 2839 $this->resetAfterTest(); 2840 $this->setAdminUser(); 2841 2842 $fieldcategory = $this->getDataGenerator()->create_custom_field_category([]); 2843 $datefield = $this->getDataGenerator()->create_custom_field([ 2844 'categoryid' => $fieldcategory->get('id'), 2845 'shortname' => 'mydate', 2846 'name' => 'My date', 2847 'type' => 'date', 2848 ]); 2849 2850 $newcourse = $this->getDataGenerator()->create_course(['customfields' => [ 2851 [ 2852 'shortname' => $datefield->get('shortname'), 2853 'value' => 1580389200, // 30/01/2020 13:00 GMT. 2854 ], 2855 ]]); 2856 2857 $result = external_api::clean_returnvalue( 2858 core_course_external::get_courses_by_field_returns(), 2859 core_course_external::get_courses_by_field('id', $newcourse->id) 2860 ); 2861 2862 $this->assertCount(1, $result['courses']); 2863 $course = reset($result['courses']); 2864 2865 $this->assertArrayHasKey('customfields', $course); 2866 $this->assertCount(1, $course['customfields']); 2867 2868 // Assert the received custom field, "value" containing a human-readable version and "valueraw" the unmodified version. 2869 $this->assertEquals([ 2870 'name' => $datefield->get('name'), 2871 'shortname' => $datefield->get('shortname'), 2872 'type' => $datefield->get('type'), 2873 'value' => userdate(1580389200), 2874 'valueraw' => 1580389200, 2875 ], reset($course['customfields'])); 2876 } 2877 2878 public function test_get_courses_by_field_invalid_field() { 2879 $this->expectException('invalid_parameter_exception'); 2880 $result = core_course_external::get_courses_by_field('zyx', 'x'); 2881 } 2882 2883 public function test_get_courses_by_field_invalid_courses() { 2884 $result = core_course_external::get_courses_by_field('id', '-1'); 2885 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2886 $this->assertCount(0, $result['courses']); 2887 } 2888 2889 /** 2890 * Test get_courses_by_field_invalid_theme_and_lang 2891 */ 2892 public function test_get_courses_by_field_invalid_theme_and_lang() { 2893 $this->resetAfterTest(true); 2894 $this->setAdminUser(); 2895 2896 $course = self::getDataGenerator()->create_course(array('theme' => 'kkt', 'lang' => 'kkl')); 2897 $result = core_course_external::get_courses_by_field('id', $course->id); 2898 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2899 $this->assertEmpty($result['courses']['0']['theme']); 2900 $this->assertEmpty($result['courses']['0']['lang']); 2901 } 2902 2903 2904 public function test_check_updates() { 2905 global $DB; 2906 $this->resetAfterTest(true); 2907 $this->setAdminUser(); 2908 2909 // Create different types of activities. 2910 $course = self::getDataGenerator()->create_course(); 2911 $tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz', 2912 'resource', 'scorm', 'survey', 'url', 'wiki'); 2913 2914 $modules = array(); 2915 foreach ($tocreate as $modname) { 2916 $modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id)); 2917 $modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid); 2918 $modules[$modname]['context'] = context_module::instance($modules[$modname]['instance']->cmid); 2919 } 2920 2921 $student = self::getDataGenerator()->create_user(); 2922 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2923 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2924 $this->setUser($student); 2925 2926 $since = time(); 2927 $this->waitForSecond(); 2928 $params = array(); 2929 foreach ($modules as $modname => $data) { 2930 $params[$data['cm']->id] = array( 2931 'contextlevel' => 'module', 2932 'id' => $data['cm']->id, 2933 'since' => $since 2934 ); 2935 } 2936 2937 // Check there is nothing updated because modules are fresh new. 2938 $result = core_course_external::check_updates($course->id, $params); 2939 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2940 $this->assertCount(0, $result['instances']); 2941 $this->assertCount(0, $result['warnings']); 2942 2943 // Test with get_updates_since the same data. 2944 $result = core_course_external::get_updates_since($course->id, $since); 2945 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 2946 $this->assertCount(0, $result['instances']); 2947 $this->assertCount(0, $result['warnings']); 2948 2949 // Update a module after a second. 2950 $this->waitForSecond(); 2951 set_coursemodule_name($modules['forum']['cm']->id, 'New forum name'); 2952 2953 $found = false; 2954 $result = core_course_external::check_updates($course->id, $params); 2955 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2956 $this->assertCount(1, $result['instances']); 2957 $this->assertCount(0, $result['warnings']); 2958 foreach ($result['instances'] as $module) { 2959 foreach ($module['updates'] as $update) { 2960 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 2961 $found = true; 2962 } 2963 } 2964 } 2965 $this->assertTrue($found); 2966 2967 // Test with get_updates_since the same data. 2968 $result = core_course_external::get_updates_since($course->id, $since); 2969 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 2970 $this->assertCount(1, $result['instances']); 2971 $this->assertCount(0, $result['warnings']); 2972 $found = false; 2973 $this->assertCount(1, $result['instances']); 2974 $this->assertCount(0, $result['warnings']); 2975 foreach ($result['instances'] as $module) { 2976 foreach ($module['updates'] as $update) { 2977 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 2978 $found = true; 2979 } 2980 } 2981 } 2982 $this->assertTrue($found); 2983 2984 // Do not retrieve the configuration field. 2985 $filter = array('files'); 2986 $found = false; 2987 $result = core_course_external::check_updates($course->id, $params, $filter); 2988 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2989 $this->assertCount(0, $result['instances']); 2990 $this->assertCount(0, $result['warnings']); 2991 $this->assertFalse($found); 2992 2993 // Add invalid cmid. 2994 $params[] = array( 2995 'contextlevel' => 'module', 2996 'id' => -2, 2997 'since' => $since 2998 ); 2999 $result = core_course_external::check_updates($course->id, $params); 3000 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 3001 $this->assertCount(1, $result['warnings']); 3002 $this->assertEquals(-2, $result['warnings'][0]['itemid']); 3003 } 3004 3005 /** 3006 * Test cases for the get_enrolled_courses_by_timeline_classification test. 3007 */ 3008 public function get_get_enrolled_courses_by_timeline_classification_test_cases():array { 3009 $now = time(); 3010 $day = 86400; 3011 3012 $coursedata = [ 3013 [ 3014 'shortname' => 'apast', 3015 'startdate' => $now - ($day * 2), 3016 'enddate' => $now - $day 3017 ], 3018 [ 3019 'shortname' => 'bpast', 3020 'startdate' => $now - ($day * 2), 3021 'enddate' => $now - $day 3022 ], 3023 [ 3024 'shortname' => 'cpast', 3025 'startdate' => $now - ($day * 2), 3026 'enddate' => $now - $day 3027 ], 3028 [ 3029 'shortname' => 'dpast', 3030 'startdate' => $now - ($day * 2), 3031 'enddate' => $now - $day 3032 ], 3033 [ 3034 'shortname' => 'epast', 3035 'startdate' => $now - ($day * 2), 3036 'enddate' => $now - $day 3037 ], 3038 [ 3039 'shortname' => 'ainprogress', 3040 'startdate' => $now - $day, 3041 'enddate' => $now + $day 3042 ], 3043 [ 3044 'shortname' => 'binprogress', 3045 'startdate' => $now - $day, 3046 'enddate' => $now + $day 3047 ], 3048 [ 3049 'shortname' => 'cinprogress', 3050 'startdate' => $now - $day, 3051 'enddate' => $now + $day 3052 ], 3053 [ 3054 'shortname' => 'dinprogress', 3055 'startdate' => $now - $day, 3056 'enddate' => $now + $day 3057 ], 3058 [ 3059 'shortname' => 'einprogress', 3060 'startdate' => $now - $day, 3061 'enddate' => $now + $day 3062 ], 3063 [ 3064 'shortname' => 'afuture', 3065 'startdate' => $now + $day 3066 ], 3067 [ 3068 'shortname' => 'bfuture', 3069 'startdate' => $now + $day 3070 ], 3071 [ 3072 'shortname' => 'cfuture', 3073 'startdate' => $now + $day 3074 ], 3075 [ 3076 'shortname' => 'dfuture', 3077 'startdate' => $now + $day 3078 ], 3079 [ 3080 'shortname' => 'efuture', 3081 'startdate' => $now + $day 3082 ] 3083 ]; 3084 3085 // Raw enrolled courses result set should be returned in this order: 3086 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast, 3087 // dfuture, dinprogress, dpast, efuture, einprogress, epast 3088 // 3089 // By classification the offset values for each record should be: 3090 // COURSE_TIMELINE_FUTURE 3091 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture) 3092 // COURSE_TIMELINE_INPROGRESS 3093 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress) 3094 // COURSE_TIMELINE_PAST 3095 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast). 3096 // 3097 // NOTE: The offset applies to the unfiltered full set of courses before the classification 3098 // filtering is done. 3099 // E.g. In our example if an offset of 2 is given then it would mean the first 3100 // two courses (afuture, ainprogress) are ignored. 3101 return [ 3102 'empty set' => [ 3103 'coursedata' => [], 3104 'classification' => 'future', 3105 'limit' => 2, 3106 'offset' => 0, 3107 'sort' => 'shortname ASC', 3108 'expectedcourses' => [], 3109 'expectednextoffset' => 0 3110 ], 3111 // COURSE_TIMELINE_FUTURE. 3112 'future not limit no offset' => [ 3113 'coursedata' => $coursedata, 3114 'classification' => 'future', 3115 'limit' => 0, 3116 'offset' => 0, 3117 'sort' => 'shortname ASC', 3118 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3119 'expectednextoffset' => 15 3120 ], 3121 'future no offset' => [ 3122 'coursedata' => $coursedata, 3123 'classification' => 'future', 3124 'limit' => 2, 3125 'offset' => 0, 3126 'sort' => 'shortname ASC', 3127 'expectedcourses' => ['afuture', 'bfuture'], 3128 'expectednextoffset' => 4 3129 ], 3130 'future offset' => [ 3131 'coursedata' => $coursedata, 3132 'classification' => 'future', 3133 'limit' => 2, 3134 'offset' => 2, 3135 'sort' => 'shortname ASC', 3136 'expectedcourses' => ['bfuture', 'cfuture'], 3137 'expectednextoffset' => 7 3138 ], 3139 'future exact limit' => [ 3140 'coursedata' => $coursedata, 3141 'classification' => 'future', 3142 'limit' => 5, 3143 'offset' => 0, 3144 'sort' => 'shortname ASC', 3145 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3146 'expectednextoffset' => 13 3147 ], 3148 'future limit less results' => [ 3149 'coursedata' => $coursedata, 3150 'classification' => 'future', 3151 'limit' => 10, 3152 'offset' => 0, 3153 'sort' => 'shortname ASC', 3154 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 3155 'expectednextoffset' => 15 3156 ], 3157 'future limit less results with offset' => [ 3158 'coursedata' => $coursedata, 3159 'classification' => 'future', 3160 'limit' => 10, 3161 'offset' => 5, 3162 'sort' => 'shortname ASC', 3163 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'], 3164 'expectednextoffset' => 15 3165 ], 3166 'all no limit or offset' => [ 3167 'coursedata' => $coursedata, 3168 'classification' => 'all', 3169 'limit' => 0, 3170 'offset' => 0, 3171 'sort' => 'shortname ASC', 3172 'expectedcourses' => [ 3173 'afuture', 3174 'ainprogress', 3175 'apast', 3176 'bfuture', 3177 'binprogress', 3178 'bpast', 3179 'cfuture', 3180 'cinprogress', 3181 'cpast', 3182 'dfuture', 3183 'dinprogress', 3184 'dpast', 3185 'efuture', 3186 'einprogress', 3187 'epast' 3188 ], 3189 'expectednextoffset' => 15 3190 ], 3191 'all limit no offset' => [ 3192 'coursedata' => $coursedata, 3193 'classification' => 'all', 3194 'limit' => 5, 3195 'offset' => 0, 3196 'sort' => 'shortname ASC', 3197 'expectedcourses' => [ 3198 'afuture', 3199 'ainprogress', 3200 'apast', 3201 'bfuture', 3202 'binprogress' 3203 ], 3204 'expectednextoffset' => 5 3205 ], 3206 'all limit and offset' => [ 3207 'coursedata' => $coursedata, 3208 'classification' => 'all', 3209 'limit' => 5, 3210 'offset' => 5, 3211 'sort' => 'shortname ASC', 3212 'expectedcourses' => [ 3213 'bpast', 3214 'cfuture', 3215 'cinprogress', 3216 'cpast', 3217 'dfuture' 3218 ], 3219 'expectednextoffset' => 10 3220 ], 3221 'all offset past result set' => [ 3222 'coursedata' => $coursedata, 3223 'classification' => 'all', 3224 'limit' => 5, 3225 'offset' => 50, 3226 'sort' => 'shortname ASC', 3227 'expectedcourses' => [], 3228 'expectednextoffset' => 50 3229 ], 3230 'all limit and offset with sort ul.timeaccess desc' => [ 3231 'coursedata' => $coursedata, 3232 'classification' => 'inprogress', 3233 'limit' => 0, 3234 'offset' => 0, 3235 'sort' => 'ul.timeaccess desc', 3236 'expectedcourses' => [ 3237 'ainprogress', 3238 'binprogress', 3239 'cinprogress', 3240 'dinprogress', 3241 'einprogress' 3242 ], 3243 'expectednextoffset' => 15 3244 ], 3245 'all limit and offset with sort sql injection for sort or 1==1' => [ 3246 'coursedata' => $coursedata, 3247 'classification' => 'all', 3248 'limit' => 5, 3249 'offset' => 5, 3250 'sort' => 'ul.timeaccess desc or 1==1', 3251 'expectedcourses' => [], 3252 'expectednextoffset' => 0, 3253 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3254 ], 3255 'all limit and offset with sql injection of sort a custom one' => [ 3256 'coursedata' => $coursedata, 3257 'classification' => 'all', 3258 'limit' => 5, 3259 'offset' => 5, 3260 'sort' => "ul.timeaccess LIMIT 1--", 3261 'expectedcourses' => [], 3262 'expectednextoffset' => 0, 3263 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3264 ], 3265 'all limit and offset with wrong sort direction' => [ 3266 'coursedata' => $coursedata, 3267 'classification' => 'all', 3268 'limit' => 5, 3269 'offset' => 5, 3270 'sort' => "ul.timeaccess abcdasc", 3271 'expectedcourses' => [], 3272 'expectednextoffset' => 0, 3273 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()' 3274 ], 3275 'all limit and offset with wrong sort direction' => [ 3276 'coursedata' => $coursedata, 3277 'classification' => 'all', 3278 'limit' => 5, 3279 'offset' => 5, 3280 'sort' => "ul.timeaccess.foo ascd", 3281 'expectedcourses' => [], 3282 'expectednextoffset' => 0, 3283 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()' 3284 ], 3285 'all limit and offset with wrong sort param' => [ 3286 'coursedata' => $coursedata, 3287 'classification' => 'all', 3288 'limit' => 5, 3289 'offset' => 5, 3290 'sort' => "foobar", 3291 'expectedcourses' => [], 3292 'expectednextoffset' => 0, 3293 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3294 ], 3295 'all limit and offset with wrong field name' => [ 3296 'coursedata' => $coursedata, 3297 'classification' => 'all', 3298 'limit' => 5, 3299 'offset' => 5, 3300 'sort' => "ul.foobar", 3301 'expectedcourses' => [], 3302 'expectednextoffset' => 0, 3303 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3304 ], 3305 'all limit and offset with wrong field separator' => [ 3306 'coursedata' => $coursedata, 3307 'classification' => 'all', 3308 'limit' => 5, 3309 'offset' => 5, 3310 'sort' => "ul.timeaccess.foo", 3311 'expectedcourses' => [], 3312 'expectednextoffset' => 0, 3313 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3314 ], 3315 'all limit and offset with wrong field separator #' => [ 3316 'coursedata' => $coursedata, 3317 'classification' => 'all', 3318 'limit' => 5, 3319 'offset' => 5, 3320 'sort' => "ul#timeaccess", 3321 'expectedcourses' => [], 3322 'expectednextoffset' => 0, 3323 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3324 ], 3325 'all limit and offset with wrong field separator $' => [ 3326 'coursedata' => $coursedata, 3327 'classification' => 'all', 3328 'limit' => 5, 3329 'offset' => 5, 3330 'sort' => 'ul$timeaccess', 3331 'expectedcourses' => [], 3332 'expectednextoffset' => 0, 3333 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3334 ], 3335 'all limit and offset with wrong field name' => [ 3336 'coursedata' => $coursedata, 3337 'classification' => 'all', 3338 'limit' => 5, 3339 'offset' => 5, 3340 'sort' => 'timeaccess123', 3341 'expectedcourses' => [], 3342 'expectednextoffset' => 0, 3343 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3344 ], 3345 'all limit and offset with no sort direction for ul' => [ 3346 'coursedata' => $coursedata, 3347 'classification' => 'inprogress', 3348 'limit' => 0, 3349 'offset' => 0, 3350 'sort' => "ul.timeaccess", 3351 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3352 'expectednextoffset' => 15, 3353 ], 3354 'all limit and offset with valid field name and no prefix, test for ul' => [ 3355 'coursedata' => $coursedata, 3356 'classification' => 'inprogress', 3357 'limit' => 0, 3358 'offset' => 0, 3359 'sort' => "timeaccess", 3360 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3361 'expectednextoffset' => 15, 3362 ], 3363 'all limit and offset with valid field name and no prefix' => [ 3364 'coursedata' => $coursedata, 3365 'classification' => 'all', 3366 'limit' => 5, 3367 'offset' => 5, 3368 'sort' => "fullname", 3369 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3370 'expectednextoffset' => 10, 3371 ], 3372 'all limit and offset with valid field name and no prefix and with sort direction' => [ 3373 'coursedata' => $coursedata, 3374 'classification' => 'all', 3375 'limit' => 5, 3376 'offset' => 5, 3377 'sort' => "fullname desc", 3378 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3379 'expectednextoffset' => 10, 3380 ], 3381 ]; 3382 } 3383 3384 /** 3385 * Test the get_enrolled_courses_by_timeline_classification function. 3386 * 3387 * @dataProvider get_get_enrolled_courses_by_timeline_classification_test_cases() 3388 * @param array $coursedata Courses to create 3389 * @param string $classification Timeline classification 3390 * @param int $limit Maximum number of results 3391 * @param int $offset Offset the unfiltered courses result set by this amount 3392 * @param string $sort sort the courses 3393 * @param array $expectedcourses Expected courses in result 3394 * @param int $expectednextoffset Expected next offset value in result 3395 * @param string|null $expectedexception Expected exception string 3396 */ 3397 public function test_get_enrolled_courses_by_timeline_classification( 3398 $coursedata, 3399 $classification, 3400 $limit, 3401 $offset, 3402 $sort, 3403 $expectedcourses, 3404 $expectednextoffset, 3405 $expectedexception = null 3406 ) { 3407 $this->resetAfterTest(); 3408 $generator = $this->getDataGenerator(); 3409 3410 $courses = array_map(function($coursedata) use ($generator) { 3411 return $generator->create_course($coursedata); 3412 }, $coursedata); 3413 3414 $student = $generator->create_user(); 3415 3416 foreach ($courses as $course) { 3417 $generator->enrol_user($student->id, $course->id, 'student'); 3418 } 3419 3420 $this->setUser($student); 3421 3422 if (isset($expectedexception)) { 3423 $this->expectException('coding_exception'); 3424 $this->expectExceptionMessage($expectedexception); 3425 } 3426 3427 // NOTE: The offset applies to the unfiltered full set of courses before the classification 3428 // filtering is done. 3429 // E.g. In our example if an offset of 2 is given then it would mean the first 3430 // two courses (afuture, ainprogress) are ignored. 3431 $result = core_course_external::get_enrolled_courses_by_timeline_classification( 3432 $classification, 3433 $limit, 3434 $offset, 3435 $sort 3436 ); 3437 $result = external_api::clean_returnvalue( 3438 core_course_external::get_enrolled_courses_by_timeline_classification_returns(), 3439 $result 3440 ); 3441 3442 $actual = array_map(function($course) { 3443 return $course['shortname']; 3444 }, $result['courses']); 3445 3446 $this->assertEqualsCanonicalizing($expectedcourses, $actual); 3447 $this->assertEquals($expectednextoffset, $result['nextoffset']); 3448 } 3449 3450 /** 3451 * Test the get_recent_courses function. 3452 */ 3453 public function test_get_recent_courses() { 3454 global $USER, $DB; 3455 3456 $this->resetAfterTest(); 3457 $generator = $this->getDataGenerator(); 3458 3459 set_config('hiddenuserfields', 'lastaccess'); 3460 3461 $courses = array(); 3462 for ($i = 1; $i < 12; $i++) { 3463 $courses[] = $generator->create_course(); 3464 }; 3465 3466 $student = $generator->create_user(); 3467 $teacher = $generator->create_user(); 3468 3469 foreach ($courses as $course) { 3470 $generator->enrol_user($student->id, $course->id, 'student'); 3471 } 3472 3473 $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher'); 3474 3475 $this->setUser($student); 3476 3477 $result = core_course_external::get_recent_courses($USER->id); 3478 3479 // No course accessed. 3480 $this->assertCount(0, $result); 3481 3482 foreach ($courses as $course) { 3483 core_course_external::view_course($course->id); 3484 } 3485 3486 // Every course accessed. 3487 $result = core_course_external::get_recent_courses($USER->id); 3488 $this->assertCount( 11, $result); 3489 3490 // Every course accessed, result limited to 10 courses. 3491 $result = core_course_external::get_recent_courses($USER->id, 10); 3492 $this->assertCount(10, $result); 3493 3494 $guestcourse = $generator->create_course( 3495 (object)array('shortname' => 'guestcourse', 3496 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, 3497 'enrol_guest_password_0' => '')); 3498 core_course_external::view_course($guestcourse->id); 3499 3500 // Every course accessed, even the not enrolled one. 3501 $result = core_course_external::get_recent_courses($USER->id); 3502 $this->assertCount(12, $result); 3503 3504 // Offset 5, return 7 out of 12. 3505 $result = core_course_external::get_recent_courses($USER->id, 0, 5); 3506 $this->assertCount(7, $result); 3507 3508 // Offset 5 and limit 3, return 3 out of 12. 3509 $result = core_course_external::get_recent_courses($USER->id, 3, 5); 3510 $this->assertCount(3, $result); 3511 3512 // Sorted by course id ASC. 3513 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id ASC'); 3514 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3515 3516 // Sorted by course id DESC. 3517 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id DESC'); 3518 $this->assertEquals($guestcourse->id, array_shift($result)->id); 3519 3520 // If last access is hidden, only get the courses where has viewhiddenuserfields capability. 3521 $this->setUser($teacher); 3522 $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 3523 $usercontext = context_user::instance($student->id); 3524 $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid); 3525 3526 // Sorted by course id DESC. 3527 $result = core_course_external::get_recent_courses($student->id); 3528 $this->assertCount(1, $result); 3529 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3530 } 3531 3532 /** 3533 * Test get enrolled users by cmid function. 3534 */ 3535 public function test_get_enrolled_users_by_cmid() { 3536 global $PAGE; 3537 $this->resetAfterTest(true); 3538 3539 $user1 = self::getDataGenerator()->create_user(); 3540 $user2 = self::getDataGenerator()->create_user(); 3541 $user3 = self::getDataGenerator()->create_user(); 3542 3543 $user1picture = new user_picture($user1); 3544 $user1picture->size = 1; 3545 $user1->profileimage = $user1picture->get_url($PAGE)->out(false); 3546 3547 $user2picture = new user_picture($user2); 3548 $user2picture->size = 1; 3549 $user2->profileimage = $user2picture->get_url($PAGE)->out(false); 3550 3551 $user3picture = new user_picture($user3); 3552 $user3picture->size = 1; 3553 $user3->profileimage = $user3picture->get_url($PAGE)->out(false); 3554 3555 // Set the first created user to the test user. 3556 self::setUser($user1); 3557 3558 // Create course to add the module. 3559 $course1 = self::getDataGenerator()->create_course(); 3560 3561 // Forum with tracking off. 3562 $record = new stdClass(); 3563 $record->course = $course1->id; 3564 $forum1 = self::getDataGenerator()->create_module('forum', $record); 3565 3566 // Following lines enrol and assign default role id to the users. 3567 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 3568 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 3569 // Enrol a suspended user in the course. 3570 $this->getDataGenerator()->enrol_user($user3->id, $course1->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED); 3571 3572 // Create what we expect to be returned when querying the course module. 3573 $expectedusers = array( 3574 'users' => array(), 3575 'warnings' => array(), 3576 ); 3577 3578 $expectedusers['users'][0] = [ 3579 'id' => $user1->id, 3580 'fullname' => fullname($user1), 3581 'firstname' => $user1->firstname, 3582 'lastname' => $user1->lastname, 3583 'profileimage' => $user1->profileimage, 3584 ]; 3585 $expectedusers['users'][1] = [ 3586 'id' => $user2->id, 3587 'fullname' => fullname($user2), 3588 'firstname' => $user2->firstname, 3589 'lastname' => $user2->lastname, 3590 'profileimage' => $user2->profileimage, 3591 ]; 3592 $expectedusers['users'][2] = [ 3593 'id' => $user3->id, 3594 'fullname' => fullname($user3), 3595 'firstname' => $user3->firstname, 3596 'lastname' => $user3->lastname, 3597 'profileimage' => $user3->profileimage, 3598 ]; 3599 3600 // Test getting the users in a given context. 3601 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid); 3602 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users); 3603 3604 $this->assertEquals(3, count($users['users'])); 3605 $this->assertEquals($expectedusers, $users); 3606 3607 // Test getting only the active users in a given context. 3608 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid, 0, true); 3609 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users); 3610 3611 $expectedusers['users'] = [ 3612 [ 3613 'id' => $user1->id, 3614 'fullname' => fullname($user1), 3615 'firstname' => $user1->firstname, 3616 'lastname' => $user1->lastname, 3617 'profileimage' => $user1->profileimage, 3618 ], 3619 [ 3620 'id' => $user2->id, 3621 'fullname' => fullname($user2), 3622 'firstname' => $user2->firstname, 3623 'lastname' => $user2->lastname, 3624 'profileimage' => $user2->profileimage, 3625 ] 3626 ]; 3627 3628 $this->assertEquals(2, count($users['users'])); 3629 $this->assertEquals($expectedusers, $users); 3630 } 3631 3632 /** 3633 * Verify that content items can be added to user favourites. 3634 */ 3635 public function test_add_content_item_to_user_favourites() { 3636 $this->resetAfterTest(); 3637 3638 $course = $this->getDataGenerator()->create_course(); 3639 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3640 $this->setUser($user); 3641 3642 // Using the internal API, confirm that no items are set as favourites for the user. 3643 $contentitemservice = new \core_course\local\service\content_item_service( 3644 new \core_course\local\repository\content_item_readonly_repository() 3645 ); 3646 $contentitems = $contentitemservice->get_all_content_items($user); 3647 $favourited = array_filter($contentitems, function($contentitem) { 3648 return $contentitem->favourite == true; 3649 }); 3650 $this->assertCount(0, $favourited); 3651 3652 // Using the external API, favourite a content item for the user. 3653 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3654 $contentitem = core_course_external::add_content_item_to_user_favourites('mod_assign', $assign->id, $user->id); 3655 $contentitem = external_api::clean_returnvalue(core_course_external::add_content_item_to_user_favourites_returns(), 3656 $contentitem); 3657 3658 // Verify the returned item is a favourite. 3659 $this->assertTrue($contentitem['favourite']); 3660 3661 // Using the internal API, confirm we see a single favourite item. 3662 $contentitems = $contentitemservice->get_all_content_items($user); 3663 $favourited = array_values(array_filter($contentitems, function($contentitem) { 3664 return $contentitem->favourite == true; 3665 })); 3666 $this->assertCount(1, $favourited); 3667 $this->assertEquals('assign', $favourited[0]->name); 3668 } 3669 3670 /** 3671 * Verify that content items can be removed from user favourites. 3672 */ 3673 public function test_remove_content_item_from_user_favourites() { 3674 $this->resetAfterTest(); 3675 3676 $course = $this->getDataGenerator()->create_course(); 3677 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3678 $this->setUser($user); 3679 3680 // Using the internal API, set a favourite for the user. 3681 $contentitemservice = new \core_course\local\service\content_item_service( 3682 new \core_course\local\repository\content_item_readonly_repository() 3683 ); 3684 $contentitems = $contentitemservice->get_all_content_items($user); 3685 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3686 $contentitemservice->add_to_user_favourites($user, $assign->componentname, $assign->id); 3687 3688 $contentitems = $contentitemservice->get_all_content_items($user); 3689 $favourited = array_filter($contentitems, function($contentitem) { 3690 return $contentitem->favourite == true; 3691 }); 3692 $this->assertCount(1, $favourited); 3693 3694 // Now, verify the external API can remove the favourite. 3695 $contentitem = core_course_external::remove_content_item_from_user_favourites('mod_assign', $assign->id); 3696 $contentitem = external_api::clean_returnvalue(core_course_external::remove_content_item_from_user_favourites_returns(), 3697 $contentitem); 3698 3699 // Verify the returned item is a favourite. 3700 $this->assertFalse($contentitem['favourite']); 3701 3702 // Using the internal API, confirm we see no favourite items. 3703 $contentitems = $contentitemservice->get_all_content_items($user); 3704 $favourited = array_filter($contentitems, function($contentitem) { 3705 return $contentitem->favourite == true; 3706 }); 3707 $this->assertCount(0, $favourited); 3708 } 3709 3710 /** 3711 * Test the web service returning course content items for inclusion in activity choosers, etc. 3712 */ 3713 public function test_get_course_content_items() { 3714 $this->resetAfterTest(); 3715 3716 $course = self::getDataGenerator()->create_course(); 3717 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3718 3719 // Fetch available content items as the editing teacher. 3720 $this->setUser($user); 3721 $result = core_course_external::get_course_content_items($course->id); 3722 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3723 3724 $contentitemservice = new \core_course\local\service\content_item_service( 3725 new \core_course\local\repository\content_item_readonly_repository() 3726 ); 3727 3728 // Check if the webservice returns exactly what the service defines, albeit in array form. 3729 $serviceitemsasarray = array_map(function($item) { 3730 return (array) $item; 3731 }, $contentitemservice->get_content_items_for_user_in_course($user, $course)); 3732 3733 $this->assertEquals($serviceitemsasarray, $result['content_items']); 3734 } 3735 3736 /** 3737 * Test the web service returning course content items, specifically in case where the user can't manage activities. 3738 */ 3739 public function test_get_course_content_items_no_permission_to_manage() { 3740 $this->resetAfterTest(); 3741 3742 $course = self::getDataGenerator()->create_course(); 3743 $user = self::getDataGenerator()->create_and_enrol($course, 'student'); 3744 3745 // Fetch available content items as a student, who won't have the permission to manage activities. 3746 $this->setUser($user); 3747 $result = core_course_external::get_course_content_items($course->id); 3748 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3749 3750 $this->assertEmpty($result['content_items']); 3751 } 3752 3753 /** 3754 * Test toggling the recommendation of an activity. 3755 */ 3756 public function test_toggle_activity_recommendation() { 3757 global $CFG; 3758 3759 $this->resetAfterTest(); 3760 3761 $context = context_system::instance(); 3762 $usercontext = context_user::instance($CFG->siteguest); 3763 $component = 'core_course'; 3764 $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext); 3765 3766 $areaname = 'test_core'; 3767 $areaid = 3; 3768 3769 // Test we have the favourite. 3770 $this->setAdminUser(); 3771 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3772 $this->assertTrue($favouritefactory->favourite_exists($component, 3773 \core_course\local\service\content_item_service::RECOMMENDATION_PREFIX . $areaname, $areaid, $context)); 3774 $this->assertTrue($result['status']); 3775 // Test that it is now gone. 3776 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3777 $this->assertFalse($favouritefactory->favourite_exists($component, $areaname, $areaid, $context)); 3778 $this->assertFalse($result['status']); 3779 } 3780 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body