See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * External course functions unit tests 19 * 20 * @package core_course 21 * @category external 22 * @copyright 2012 Jerome Mouneyrac 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 30 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 31 32 /** 33 * External course functions unit tests 34 * 35 * @package core_course 36 * @category external 37 * @copyright 2012 Jerome Mouneyrac 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class core_course_externallib_testcase extends externallib_advanced_testcase { 41 42 /** 43 * Tests set up 44 */ 45 protected function setUp() { 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->assertEquals(array_keys($generatedcats), $returnedids, '', 0.0, 10, true); 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->expectExceptionMessageRegExp("/{$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->assertContains($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 if ($dbcourse->id == 4) { 817 $this->assertEquals($course['customfields'], [array_merge($customfield, $customfieldvalue)]); 818 } 819 } 820 821 // Get all courses in the DB 822 $courses = core_course_external::get_courses(array()); 823 824 // We need to execute the return values cleaning process to simulate the web service server. 825 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 826 827 $this->assertEquals($DB->count_records('course'), count($courses)); 828 } 829 830 /** 831 * Test get_courses without capability 832 */ 833 public function test_get_courses_without_capability() { 834 $this->resetAfterTest(true); 835 836 $course1 = $this->getDataGenerator()->create_course(); 837 $this->setUser($this->getDataGenerator()->create_user()); 838 839 // No permissions are required to get the site course. 840 $courses = core_course_external::get_courses(array('ids' => [SITEID])); 841 $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses); 842 843 $this->assertEquals(1, count($courses)); 844 $this->assertEquals('PHPUnit test site', $courses[0]['fullname']); 845 $this->assertEquals('site', $courses[0]['format']); 846 847 // Requesting course without being enrolled or capability to view it will throw an exception. 848 try { 849 core_course_external::get_courses(array('ids' => [$course1->id])); 850 $this->fail('Exception expected'); 851 } catch (moodle_exception $e) { 852 $this->assertEquals(1, preg_match('/Course or activity not accessible. \(Not enrolled\)/', $e->getMessage())); 853 } 854 } 855 856 /** 857 * Test search_courses 858 */ 859 public function test_search_courses () { 860 861 global $DB; 862 863 $this->resetAfterTest(true); 864 $this->setAdminUser(); 865 $generatedcourses = array(); 866 $coursedata1['fullname'] = 'FIRST COURSE'; 867 $course1 = self::getDataGenerator()->create_course($coursedata1); 868 869 $page = new moodle_page(); 870 $page->set_course($course1); 871 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 872 873 $coursedata2['fullname'] = 'SECOND COURSE'; 874 $course2 = self::getDataGenerator()->create_course($coursedata2); 875 876 $page = new moodle_page(); 877 $page->set_course($course2); 878 $page->blocks->add_blocks([BLOCK_POS_LEFT => ['news_items'], BLOCK_POS_RIGHT => []], 'course-view-*'); 879 880 // Search by name. 881 $results = core_course_external::search_courses('search', 'FIRST'); 882 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 883 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 884 $this->assertCount(1, $results['courses']); 885 886 // Create the forum. 887 $record = new stdClass(); 888 $record->introformat = FORMAT_HTML; 889 $record->course = $course2->id; 890 // Set Aggregate type = Average of ratings. 891 $forum = self::getDataGenerator()->create_module('forum', $record); 892 893 // Search by module. 894 $results = core_course_external::search_courses('modulelist', 'forum'); 895 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 896 $this->assertEquals(1, $results['total']); 897 898 // Enable coursetag option. 899 set_config('block_tags_showcoursetags', true); 900 // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2. 901 core_tag_tag::set_item_tags('core', 'course', $course2->id, context_course::instance($course2->id), 902 array('TAG-LABEL ON SECOND COURSE')); 903 $taginstance = $DB->get_record('tag_instance', 904 array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST); 905 906 // Search by tagid. 907 $results = core_course_external::search_courses('tagid', $taginstance->tagid); 908 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 909 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 910 911 // Search by block (use news_items default block). 912 $blockid = $DB->get_field('block', 'id', array('name' => 'news_items')); 913 $results = core_course_external::search_courses('blocklist', $blockid); 914 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 915 $this->assertEquals(2, $results['total']); 916 917 // Now as a normal user. 918 $user = self::getDataGenerator()->create_user(); 919 920 // Add a 3rd, hidden, course we shouldn't see, even when enrolled as student. 921 $coursedata3['fullname'] = 'HIDDEN COURSE'; 922 $coursedata3['visible'] = 0; 923 $course3 = self::getDataGenerator()->create_course($coursedata3); 924 $this->getDataGenerator()->enrol_user($user->id, $course3->id, 'student'); 925 926 $this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student'); 927 $this->setUser($user); 928 929 $results = core_course_external::search_courses('search', 'FIRST'); 930 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 931 $this->assertCount(1, $results['courses']); 932 $this->assertEquals(1, $results['total']); 933 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']); 934 935 // Check that we can see all courses without the limit to enrolled setting. 936 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 0); 937 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 938 $this->assertCount(2, $results['courses']); 939 $this->assertEquals(2, $results['total']); 940 941 // Check that we only see our enrolled course when limiting. 942 $results = core_course_external::search_courses('search', 'COURSE', 0, 0, array(), 1); 943 $results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results); 944 $this->assertCount(1, $results['courses']); 945 $this->assertEquals(1, $results['total']); 946 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']); 947 948 // Search by block (use news_items default block). Should fail (only admins allowed). 949 $this->expectException('required_capability_exception'); 950 $results = core_course_external::search_courses('blocklist', $blockid); 951 } 952 953 /** 954 * Create a course with contents 955 * @return array A list with the course object and course modules objects 956 */ 957 private function prepare_get_course_contents_test() { 958 global $DB, $CFG; 959 960 $CFG->allowstealth = 1; // Allow stealth activities. 961 $CFG->enablecompletion = true; 962 // Course with 4 sections (apart from the main section), with completion and not displaying hidden sections. 963 $course = self::getDataGenerator()->create_course(['numsections' => 4, 'enablecompletion' => 1, 'hiddensections' => 1]); 964 965 $forumdescription = 'This is the forum description'; 966 $forum = $this->getDataGenerator()->create_module('forum', 967 array('course' => $course->id, 'intro' => $forumdescription, 'trackingtype' => 2), 968 array('showdescription' => true, 'completion' => COMPLETION_TRACKING_MANUAL)); 969 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 970 // Add discussions to the tracking forced forum. 971 $record = new stdClass(); 972 $record->course = $course->id; 973 $record->userid = 0; 974 $record->forum = $forum->id; 975 $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 976 $data = $this->getDataGenerator()->create_module('data', 977 array('assessed' => 1, 'scale' => 100, 'course' => $course->id, 'completion' => 2, 'completionentries' => 3)); 978 $datacm = get_coursemodule_from_instance('data', $data->id); 979 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 980 $pagecm = get_coursemodule_from_instance('page', $page->id); 981 // This is an stealth page (set by visibleoncoursepage). 982 $pagestealth = $this->getDataGenerator()->create_module('page', array('course' => $course->id, 'visibleoncoursepage' => 0)); 983 $labeldescription = 'This is a very long label to test if more than 50 characters are returned. 984 So bla bla bla bla <b>bold bold bold</b> bla bla bla bla.'; 985 $label = $this->getDataGenerator()->create_module('label', array('course' => $course->id, 986 'intro' => $labeldescription, 'completion' => COMPLETION_TRACKING_MANUAL)); 987 $labelcm = get_coursemodule_from_instance('label', $label->id); 988 $tomorrow = time() + DAYSECS; 989 // Module with availability restrictions not met. 990 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '},' 991 .'{"type":"completion","cm":' . $label->cmid .',"e":1}],"showc":[true,true]}'; 992 $url = $this->getDataGenerator()->create_module('url', 993 array('course' => $course->id, 'name' => 'URL: % & $ ../', 'section' => 2, 'display' => RESOURCELIB_DISPLAY_POPUP, 994 'popupwidth' => 100, 'popupheight' => 100), 995 array('availability' => $availability)); 996 $urlcm = get_coursemodule_from_instance('url', $url->id); 997 // Module for the last section. 998 $this->getDataGenerator()->create_module('url', 999 array('course' => $course->id, 'name' => 'URL for last section', 'section' => 3)); 1000 // Module for section 1 with availability restrictions met. 1001 $yesterday = time() - DAYSECS; 1002 $this->getDataGenerator()->create_module('url', 1003 array('course' => $course->id, 'name' => 'URL restrictions met', 'section' => 1), 1004 array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":'. $yesterday .'}],"showc":[true]}')); 1005 1006 // Set the required capabilities by the external function. 1007 $context = context_course::instance($course->id); 1008 $roleid = $this->assignUserCapability('moodle/course:view', $context->id); 1009 $this->assignUserCapability('moodle/course:update', $context->id, $roleid); 1010 $this->assignUserCapability('mod/data:view', $context->id, $roleid); 1011 1012 $conditions = array('course' => $course->id, 'section' => 2); 1013 $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions); 1014 1015 // Add date availability condition not met for section 3. 1016 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '}],"showc":[true]}'; 1017 $DB->set_field('course_sections', 'availability', $availability, 1018 array('course' => $course->id, 'section' => 3)); 1019 1020 // Create resource for last section. 1021 $pageinhiddensection = $this->getDataGenerator()->create_module('page', 1022 array('course' => $course->id, 'name' => 'Page in hidden section', 'section' => 4)); 1023 // Set not visible last section. 1024 $DB->set_field('course_sections', 'visible', 0, 1025 array('course' => $course->id, 'section' => 4)); 1026 1027 rebuild_course_cache($course->id, true); 1028 1029 return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm); 1030 } 1031 1032 /** 1033 * Test get_course_contents 1034 */ 1035 public function test_get_course_contents() { 1036 global $CFG; 1037 $this->resetAfterTest(true); 1038 1039 $CFG->forum_allowforcedreadtracking = 1; 1040 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1041 1042 // We first run the test as admin. 1043 $this->setAdminUser(); 1044 $sections = core_course_external::get_course_contents($course->id, array()); 1045 // We need to execute the return values cleaning process to simulate the web service server. 1046 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1047 1048 $modinfo = get_fast_modinfo($course); 1049 $testexecuted = 0; 1050 foreach ($sections[0]['modules'] as $module) { 1051 if ($module['id'] == $forumcm->id and $module['modname'] == 'forum') { 1052 $cm = $modinfo->cms[$forumcm->id]; 1053 $formattedtext = format_text($cm->content, FORMAT_HTML, 1054 array('noclean' => true, 'para' => false, 'filter' => false)); 1055 $this->assertEquals($formattedtext, $module['description']); 1056 $this->assertEquals($forumcm->instance, $module['instance']); 1057 $this->assertContains('1 unread post', $module['afterlink']); 1058 $this->assertFalse($module['noviewlink']); 1059 $this->assertNotEmpty($module['description']); // Module showdescription is on. 1060 $testexecuted = $testexecuted + 2; 1061 } else if ($module['id'] == $labelcm->id and $module['modname'] == 'label') { 1062 $cm = $modinfo->cms[$labelcm->id]; 1063 $formattedtext = format_text($cm->content, FORMAT_HTML, 1064 array('noclean' => true, 'para' => false, 'filter' => false)); 1065 $this->assertEquals($formattedtext, $module['description']); 1066 $this->assertEquals($labelcm->instance, $module['instance']); 1067 $this->assertTrue($module['noviewlink']); 1068 $this->assertNotEmpty($module['description']); // Label always prints the description. 1069 $testexecuted = $testexecuted + 1; 1070 } else if ($module['id'] == $datacm->id and $module['modname'] == 'data') { 1071 $this->assertContains('customcompletionrules', $module['customdata']); 1072 $this->assertFalse($module['noviewlink']); 1073 $this->assertArrayNotHasKey('description', $module); 1074 $testexecuted = $testexecuted + 1; 1075 } 1076 } 1077 foreach ($sections[2]['modules'] as $module) { 1078 if ($module['id'] == $urlcm->id and $module['modname'] == 'url') { 1079 $this->assertContains('width=100,height=100', $module['onclick']); 1080 $testexecuted = $testexecuted + 1; 1081 } 1082 } 1083 1084 $CFG->forum_allowforcedreadtracking = 0; // Recover original value. 1085 forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests. 1086 1087 $this->assertEquals(5, $testexecuted); 1088 $this->assertEquals(0, $sections[0]['section']); 1089 1090 $this->assertCount(5, $sections[0]['modules']); 1091 $this->assertCount(1, $sections[1]['modules']); 1092 $this->assertCount(1, $sections[2]['modules']); 1093 $this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions. 1094 $this->assertCount(1, $sections[4]['modules']); // One module for the hidden section with a visible activity. 1095 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1096 $this->assertEquals(1, $sections[1]['section']); 1097 $this->assertEquals(2, $sections[2]['section']); 1098 $this->assertEquals(3, $sections[3]['section']); 1099 $this->assertEquals(4, $sections[4]['section']); 1100 $this->assertContains('<iframe', $sections[2]['summary']); 1101 $this->assertContains('</iframe>', $sections[2]['summary']); 1102 $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']); 1103 try { 1104 $sections = core_course_external::get_course_contents($course->id, 1105 array(array("name" => "invalid", "value" => 1))); 1106 $this->fail('Exception expected due to invalid option.'); 1107 } catch (moodle_exception $e) { 1108 $this->assertEquals('errorinvalidparam', $e->errorcode); 1109 } 1110 } 1111 1112 1113 /** 1114 * Test get_course_contents as student 1115 */ 1116 public function test_get_course_contents_student() { 1117 global $DB; 1118 $this->resetAfterTest(true); 1119 1120 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1121 1122 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1123 $user = self::getDataGenerator()->create_user(); 1124 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1125 $this->setUser($user); 1126 1127 $sections = core_course_external::get_course_contents($course->id, array()); 1128 // We need to execute the return values cleaning process to simulate the web service server. 1129 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1130 1131 $this->assertCount(4, $sections); // Nothing for the not visible section. 1132 $this->assertCount(5, $sections[0]['modules']); 1133 $this->assertCount(1, $sections[1]['modules']); 1134 $this->assertCount(1, $sections[2]['modules']); 1135 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1136 1137 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1138 $this->assertEquals(1, $sections[1]['section']); 1139 $this->assertEquals(2, $sections[2]['section']); 1140 $this->assertEquals(3, $sections[3]['section']); 1141 // The module with the availability restriction met is returning contents. 1142 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1143 // The module with the availability restriction not met is not returning contents. 1144 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1145 1146 // Now include flag for returning stealth information (fake section). 1147 $sections = core_course_external::get_course_contents($course->id, 1148 array(array("name" => "includestealthmodules", "value" => 1))); 1149 // We need to execute the return values cleaning process to simulate the web service server. 1150 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1151 1152 $this->assertCount(5, $sections); // Include fake section with stealth activities. 1153 $this->assertCount(5, $sections[0]['modules']); 1154 $this->assertCount(1, $sections[1]['modules']); 1155 $this->assertCount(1, $sections[2]['modules']); 1156 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1157 $this->assertCount(1, $sections[4]['modules']); // One stealth module. 1158 $this->assertEquals(-1, $sections[4]['id']); 1159 } 1160 1161 /** 1162 * Test get_course_contents excluding modules 1163 */ 1164 public function test_get_course_contents_excluding_modules() { 1165 $this->resetAfterTest(true); 1166 1167 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1168 1169 // Test exclude modules. 1170 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludemodules", "value" => 1))); 1171 1172 // We need to execute the return values cleaning process to simulate the web service server. 1173 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1174 1175 $this->assertEmpty($sections[0]['modules']); 1176 $this->assertEmpty($sections[1]['modules']); 1177 } 1178 1179 /** 1180 * Test get_course_contents excluding contents 1181 */ 1182 public function test_get_course_contents_excluding_contents() { 1183 $this->resetAfterTest(true); 1184 1185 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1186 1187 // Test exclude modules. 1188 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludecontents", "value" => 1))); 1189 1190 // We need to execute the return values cleaning process to simulate the web service server. 1191 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1192 1193 foreach ($sections as $section) { 1194 foreach ($section['modules'] as $module) { 1195 // Only resources return contents. 1196 if (isset($module['contents'])) { 1197 $this->assertEmpty($module['contents']); 1198 } 1199 } 1200 } 1201 } 1202 1203 /** 1204 * Test get_course_contents filtering by section number 1205 */ 1206 public function test_get_course_contents_section_number() { 1207 $this->resetAfterTest(true); 1208 1209 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1210 1211 // Test exclude modules. 1212 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "sectionnumber", "value" => 0))); 1213 1214 // We need to execute the return values cleaning process to simulate the web service server. 1215 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1216 1217 $this->assertCount(1, $sections); 1218 $this->assertCount(5, $sections[0]['modules']); 1219 } 1220 1221 /** 1222 * Test get_course_contents filtering by cmid 1223 */ 1224 public function test_get_course_contents_cmid() { 1225 $this->resetAfterTest(true); 1226 1227 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1228 1229 // Test exclude modules. 1230 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "cmid", "value" => $forumcm->id))); 1231 1232 // We need to execute the return values cleaning process to simulate the web service server. 1233 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1234 1235 $this->assertCount(4, $sections); 1236 $this->assertCount(1, $sections[0]['modules']); 1237 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1238 } 1239 1240 1241 /** 1242 * Test get_course_contents filtering by cmid and section 1243 */ 1244 public function test_get_course_contents_section_cmid() { 1245 $this->resetAfterTest(true); 1246 1247 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1248 1249 // Test exclude modules. 1250 $sections = core_course_external::get_course_contents($course->id, array( 1251 array("name" => "cmid", "value" => $forumcm->id), 1252 array("name" => "sectionnumber", "value" => 0) 1253 )); 1254 1255 // We need to execute the return values cleaning process to simulate the web service server. 1256 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1257 1258 $this->assertCount(1, $sections); 1259 $this->assertCount(1, $sections[0]['modules']); 1260 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1261 } 1262 1263 /** 1264 * Test get_course_contents filtering by modname 1265 */ 1266 public function test_get_course_contents_modname() { 1267 $this->resetAfterTest(true); 1268 1269 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1270 1271 // Test exclude modules. 1272 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "modname", "value" => "forum"))); 1273 1274 // We need to execute the return values cleaning process to simulate the web service server. 1275 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1276 1277 $this->assertCount(4, $sections); 1278 $this->assertCount(1, $sections[0]['modules']); 1279 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]); 1280 } 1281 1282 /** 1283 * Test get_course_contents filtering by modname 1284 */ 1285 public function test_get_course_contents_modid() { 1286 $this->resetAfterTest(true); 1287 1288 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1289 1290 // Test exclude modules. 1291 $sections = core_course_external::get_course_contents($course->id, array( 1292 array("name" => "modname", "value" => "page"), 1293 array("name" => "modid", "value" => $pagecm->instance), 1294 )); 1295 1296 // We need to execute the return values cleaning process to simulate the web service server. 1297 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1298 1299 $this->assertCount(4, $sections); 1300 $this->assertCount(1, $sections[0]['modules']); 1301 $this->assertEquals("page", $sections[0]['modules'][0]["modname"]); 1302 $this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]); 1303 } 1304 1305 /** 1306 * Test get course contents completion 1307 */ 1308 public function test_get_course_contents_completion() { 1309 global $CFG; 1310 $this->resetAfterTest(true); 1311 1312 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1313 availability_completion\condition::wipe_static_cache(); 1314 1315 // Test activity not completed yet. 1316 $result = core_course_external::get_course_contents($course->id, array( 1317 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1318 // We need to execute the return values cleaning process to simulate the web service server. 1319 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1320 1321 $this->assertCount(1, $result[0]['modules']); 1322 $this->assertEquals("forum", $result[0]['modules'][0]["modname"]); 1323 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1324 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']); 1325 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']); 1326 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']); 1327 $this->assertFalse($result[0]['modules'][0]["completiondata"]['valueused']); 1328 1329 // Set activity completed. 1330 core_completion_external::update_activity_completion_status_manually($forumcm->id, true); 1331 1332 $result = core_course_external::get_course_contents($course->id, array( 1333 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1334 // We need to execute the return values cleaning process to simulate the web service server. 1335 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1336 1337 $this->assertEquals(COMPLETION_COMPLETE, $result[0]['modules'][0]["completiondata"]['state']); 1338 $this->assertNotEmpty($result[0]['modules'][0]["completiondata"]['timecompleted']); 1339 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']); 1340 1341 // Test activity with completion value that is used in an availability condition. 1342 $result = core_course_external::get_course_contents($course->id, array( 1343 array("name" => "modname", "value" => "label"), array("name" => "modid", "value" => $labelcm->instance))); 1344 // We need to execute the return values cleaning process to simulate the web service server. 1345 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1346 1347 $this->assertCount(1, $result[0]['modules']); 1348 $this->assertEquals("label", $result[0]['modules'][0]["modname"]); 1349 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]); 1350 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']); 1351 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']); 1352 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']); 1353 $this->assertTrue($result[0]['modules'][0]["completiondata"]['valueused']); 1354 1355 // Disable completion. 1356 $CFG->enablecompletion = 0; 1357 $result = core_course_external::get_course_contents($course->id, array( 1358 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance))); 1359 // We need to execute the return values cleaning process to simulate the web service server. 1360 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1361 1362 $this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]); 1363 } 1364 1365 /** 1366 * Test mimetype is returned for resources with showtype set. 1367 */ 1368 public function test_get_course_contents_including_mimetype() { 1369 $this->resetAfterTest(true); 1370 1371 $this->setAdminUser(); 1372 $course = self::getDataGenerator()->create_course(); 1373 1374 $record = new stdClass(); 1375 $record->course = $course->id; 1376 $record->showtype = 1; 1377 $resource = self::getDataGenerator()->create_module('resource', $record); 1378 1379 $result = core_course_external::get_course_contents($course->id); 1380 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1381 $this->assertCount(1, $result[0]['modules']); // One module, first section. 1382 $customdata = unserialize(json_decode($result[0]['modules'][0]['customdata'])); 1383 $this->assertEquals('text/plain', $customdata['filedetails']['mimetype']); 1384 } 1385 1386 /** 1387 * Test contents info is returned. 1388 */ 1389 public function test_get_course_contents_contentsinfo() { 1390 global $USER; 1391 1392 $this->resetAfterTest(true); 1393 $this->setAdminUser(); 1394 $timenow = time(); 1395 1396 $course = self::getDataGenerator()->create_course(); 1397 1398 $record = new stdClass(); 1399 $record->course = $course->id; 1400 // One resource with one file. 1401 $resource1 = self::getDataGenerator()->create_module('resource', $record); 1402 1403 // More type of files. 1404 $record->files = file_get_unused_draft_itemid(); 1405 $usercontext = context_user::instance($USER->id); 1406 $extensions = array('txt', 'png', 'pdf'); 1407 $fs = get_file_storage(); 1408 foreach ($extensions as $key => $extension) { 1409 // Add actual file there. 1410 $filerecord = array('component' => 'user', 'filearea' => 'draft', 1411 'contextid' => $usercontext->id, 'itemid' => $record->files, 1412 'filename' => 'resource' . $key . '.' . $extension, 'filepath' => '/'); 1413 $fs->create_file_from_string($filerecord, 'Test resource ' . $key . ' file'); 1414 } 1415 1416 // Create file reference. 1417 $repos = repository::get_instances(array('type' => 'user')); 1418 $userrepository = reset($repos); 1419 1420 // Create a user private file. 1421 $userfilerecord = new stdClass; 1422 $userfilerecord->contextid = $usercontext->id; 1423 $userfilerecord->component = 'user'; 1424 $userfilerecord->filearea = 'private'; 1425 $userfilerecord->itemid = 0; 1426 $userfilerecord->filepath = '/'; 1427 $userfilerecord->filename = 'userfile.txt'; 1428 $userfilerecord->source = 'test'; 1429 $userfile = $fs->create_file_from_string($userfilerecord, 'User file content'); 1430 $userfileref = $fs->pack_reference($userfilerecord); 1431 1432 // Clone latest "normal" file. 1433 $filerefrecord = clone (object) $filerecord; 1434 $filerefrecord->filename = 'testref.txt'; 1435 $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref); 1436 // Set main file pointing to the file reference. 1437 file_set_sortorder($usercontext->id, 'user', 'draft', $record->files, $filerefrecord->filepath, 1438 $filerefrecord->filename, 1); 1439 1440 // Once the reference has been created, create the file resource. 1441 $resource2 = self::getDataGenerator()->create_module('resource', $record); 1442 1443 $result = core_course_external::get_course_contents($course->id); 1444 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result); 1445 $this->assertCount(2, $result[0]['modules']); 1446 foreach ($result[0]['modules'] as $module) { 1447 if ($module['instance'] == $resource1->id) { 1448 $this->assertEquals(1, $module['contentsinfo']['filescount']); 1449 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1450 $this->assertEquals($module['contents'][0]['filesize'], $module['contentsinfo']['filessize']); 1451 $this->assertEquals(array('text/plain'), $module['contentsinfo']['mimetypes']); 1452 } else { 1453 $this->assertEquals(count($extensions) + 1, $module['contentsinfo']['filescount']); 1454 $filessize = $module['contents'][0]['filesize'] + $module['contents'][1]['filesize'] + 1455 $module['contents'][2]['filesize'] + $module['contents'][3]['filesize']; 1456 $this->assertEquals($filessize, $module['contentsinfo']['filessize']); 1457 $this->assertEquals('user', $module['contentsinfo']['repositorytype']); 1458 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']); 1459 $this->assertEquals(array('text/plain', 'image/png', 'application/pdf'), $module['contentsinfo']['mimetypes']); 1460 } 1461 } 1462 } 1463 1464 /** 1465 * Test get_course_contents when hidden sections are displayed. 1466 */ 1467 public function test_get_course_contents_hiddensections() { 1468 global $DB; 1469 $this->resetAfterTest(true); 1470 1471 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test(); 1472 // Force returning hidden sections. 1473 $course->hiddensections = 0; 1474 update_course($course); 1475 1476 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 1477 $user = self::getDataGenerator()->create_user(); 1478 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid); 1479 $this->setUser($user); 1480 1481 $sections = core_course_external::get_course_contents($course->id, array()); 1482 // We need to execute the return values cleaning process to simulate the web service server. 1483 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1484 1485 $this->assertCount(5, $sections); // All the sections, including the "not visible" one. 1486 $this->assertCount(5, $sections[0]['modules']); 1487 $this->assertCount(1, $sections[1]['modules']); 1488 $this->assertCount(1, $sections[2]['modules']); 1489 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1490 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1491 1492 $this->assertNotEmpty($sections[3]['availabilityinfo']); 1493 $this->assertEquals(1, $sections[1]['section']); 1494 $this->assertEquals(2, $sections[2]['section']); 1495 $this->assertEquals(3, $sections[3]['section']); 1496 // The module with the availability restriction met is returning contents. 1497 $this->assertNotEmpty($sections[1]['modules'][0]['contents']); 1498 // The module with the availability restriction not met is not returning contents. 1499 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]); 1500 1501 // Now include flag for returning stealth information (fake section). 1502 $sections = core_course_external::get_course_contents($course->id, 1503 array(array("name" => "includestealthmodules", "value" => 1))); 1504 // We need to execute the return values cleaning process to simulate the web service server. 1505 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections); 1506 1507 $this->assertCount(6, $sections); // Include fake section with stealth activities. 1508 $this->assertCount(5, $sections[0]['modules']); 1509 $this->assertCount(1, $sections[1]['modules']); 1510 $this->assertCount(1, $sections[2]['modules']); 1511 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions. 1512 $this->assertCount(0, $sections[4]['modules']); // No modules for the section hidden. 1513 $this->assertCount(1, $sections[5]['modules']); // One stealth module. 1514 $this->assertEquals(-1, $sections[5]['id']); 1515 } 1516 1517 /** 1518 * Test duplicate_course 1519 */ 1520 public function test_duplicate_course() { 1521 $this->resetAfterTest(true); 1522 1523 // Create one course with three modules. 1524 $course = self::getDataGenerator()->create_course(); 1525 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id)); 1526 $forumcm = get_coursemodule_from_id('forum', $forum->cmid); 1527 $forumcontext = context_module::instance($forum->cmid); 1528 $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id)); 1529 $datacontext = context_module::instance($data->cmid); 1530 $datacm = get_coursemodule_from_instance('page', $data->id); 1531 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id)); 1532 $pagecontext = context_module::instance($page->cmid); 1533 $pagecm = get_coursemodule_from_instance('page', $page->id); 1534 1535 // Set the required capabilities by the external function. 1536 $coursecontext = context_course::instance($course->id); 1537 $categorycontext = context_coursecat::instance($course->category); 1538 $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id); 1539 $this->assignUserCapability('moodle/course:view', $categorycontext->id, $roleid); 1540 $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id, $roleid); 1541 $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id, $roleid); 1542 $this->assignUserCapability('moodle/backup:configure', $coursecontext->id, $roleid); 1543 // Optional capabilities to copy user data. 1544 $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id, $roleid); 1545 $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id, $roleid); 1546 1547 $newcourse['fullname'] = 'Course duplicate'; 1548 $newcourse['shortname'] = 'courseduplicate'; 1549 $newcourse['categoryid'] = $course->category; 1550 $newcourse['visible'] = true; 1551 $newcourse['options'][] = array('name' => 'users', 'value' => true); 1552 1553 $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'], 1554 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']); 1555 1556 // We need to execute the return values cleaning process to simulate the web service server. 1557 $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate); 1558 1559 // Check that the course has been duplicated. 1560 $this->assertEquals($newcourse['shortname'], $duplicate['shortname']); 1561 } 1562 1563 /** 1564 * Test update_courses 1565 */ 1566 public function test_update_courses() { 1567 global $DB, $CFG, $USER, $COURSE; 1568 1569 // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this 1570 // trick because we are both updating and getting (for testing) course information 1571 // in the same request and core_course_external::update_courses() 1572 // is overwriting $COURSE all over the time with OLD values, so later 1573 // use of get_course() fetches those OLD values instead of the updated ones. 1574 // See MDL-39723 for more info. 1575 $origcourse = clone($COURSE); 1576 1577 $this->resetAfterTest(true); 1578 1579 // Set the required capabilities by the external function. 1580 $contextid = context_system::instance()->id; 1581 $roleid = $this->assignUserCapability('moodle/course:update', $contextid); 1582 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1583 $this->assignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 1584 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1585 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1586 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1587 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1588 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid); 1589 $this->assignUserCapability('moodle/course:viewhiddencourses', $contextid, $roleid); 1590 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid); 1591 1592 // Create category and courses. 1593 $category1 = self::getDataGenerator()->create_category(); 1594 $category2 = self::getDataGenerator()->create_category(); 1595 1596 $originalcourse1 = self::getDataGenerator()->create_course(); 1597 self::getDataGenerator()->enrol_user($USER->id, $originalcourse1->id, $roleid); 1598 1599 $originalcourse2 = self::getDataGenerator()->create_course(); 1600 self::getDataGenerator()->enrol_user($USER->id, $originalcourse2->id, $roleid); 1601 1602 // Course with custom fields. 1603 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 1604 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 1605 'categoryid' => $fieldcategory->get('id'), 1606 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL, 'locked' => 1]]; 1607 $field = self::getDataGenerator()->create_custom_field($customfield); 1608 1609 $originalcourse3 = self::getDataGenerator()->create_course(['customfield_test' => 'Test value']); 1610 self::getDataGenerator()->enrol_user($USER->id, $originalcourse3->id, $roleid); 1611 1612 // Course values to be updated. 1613 $course1['id'] = $originalcourse1->id; 1614 $course1['fullname'] = 'Updated test course 1'; 1615 $course1['shortname'] = 'Udestedtestcourse1'; 1616 $course1['categoryid'] = $category1->id; 1617 1618 $course2['id'] = $originalcourse2->id; 1619 $course2['fullname'] = 'Updated test course 2'; 1620 $course2['shortname'] = 'Updestedtestcourse2'; 1621 $course2['categoryid'] = $category2->id; 1622 $course2['idnumber'] = 'Updatedidnumber2'; 1623 $course2['summary'] = 'Updaated description for course 2'; 1624 $course2['summaryformat'] = FORMAT_HTML; 1625 $course2['format'] = 'topics'; 1626 $course2['showgrades'] = 1; 1627 $course2['newsitems'] = 3; 1628 $course2['startdate'] = 1420092000; // 01/01/2015. 1629 $course2['enddate'] = 1422669600; // 01/31/2015. 1630 $course2['maxbytes'] = 100000; 1631 $course2['showreports'] = 1; 1632 $course2['visible'] = 0; 1633 $course2['hiddensections'] = 0; 1634 $course2['groupmode'] = 0; 1635 $course2['groupmodeforce'] = 0; 1636 $course2['defaultgroupingid'] = 0; 1637 $course2['enablecompletion'] = 1; 1638 $course2['lang'] = 'en'; 1639 $course2['forcetheme'] = 'classic'; 1640 1641 $course3['id'] = $originalcourse3->id; 1642 $updatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'Updated test value']; 1643 $course3['customfields'] = [$updatedcustomfieldvalue]; 1644 $courses = array($course1, $course2, $course3); 1645 1646 $updatedcoursewarnings = core_course_external::update_courses($courses); 1647 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1648 $updatedcoursewarnings); 1649 $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line. 1650 1651 // Check that right number of courses were created. 1652 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1653 1654 // Check that the courses were correctly created. 1655 foreach ($courses as $course) { 1656 $courseinfo = course_get_format($course['id'])->get_course(); 1657 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course['id']); 1658 if ($course['id'] == $course2['id']) { 1659 $this->assertEquals($course2['fullname'], $courseinfo->fullname); 1660 $this->assertEquals($course2['shortname'], $courseinfo->shortname); 1661 $this->assertEquals($course2['categoryid'], $courseinfo->category); 1662 $this->assertEquals($course2['idnumber'], $courseinfo->idnumber); 1663 $this->assertEquals($course2['summary'], $courseinfo->summary); 1664 $this->assertEquals($course2['summaryformat'], $courseinfo->summaryformat); 1665 $this->assertEquals($course2['format'], $courseinfo->format); 1666 $this->assertEquals($course2['showgrades'], $courseinfo->showgrades); 1667 $this->assertEquals($course2['newsitems'], $courseinfo->newsitems); 1668 $this->assertEquals($course2['startdate'], $courseinfo->startdate); 1669 $this->assertEquals($course2['enddate'], $courseinfo->enddate); 1670 $this->assertEquals($course2['maxbytes'], $courseinfo->maxbytes); 1671 $this->assertEquals($course2['showreports'], $courseinfo->showreports); 1672 $this->assertEquals($course2['visible'], $courseinfo->visible); 1673 $this->assertEquals($course2['hiddensections'], $courseinfo->hiddensections); 1674 $this->assertEquals($course2['groupmode'], $courseinfo->groupmode); 1675 $this->assertEquals($course2['groupmodeforce'], $courseinfo->groupmodeforce); 1676 $this->assertEquals($course2['defaultgroupingid'], $courseinfo->defaultgroupingid); 1677 $this->assertEquals($course2['lang'], $courseinfo->lang); 1678 1679 if (!empty($CFG->allowcoursethemes)) { 1680 $this->assertEquals($course2['forcetheme'], $courseinfo->theme); 1681 } 1682 1683 $this->assertEquals($course2['enablecompletion'], $courseinfo->enablecompletion); 1684 $this->assertEquals(['test' => null], (array)$customfields); 1685 } else if ($course['id'] == $course1['id']) { 1686 $this->assertEquals($course1['fullname'], $courseinfo->fullname); 1687 $this->assertEquals($course1['shortname'], $courseinfo->shortname); 1688 $this->assertEquals($course1['categoryid'], $courseinfo->category); 1689 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1690 $this->assertEquals('topics', $courseinfo->format); 1691 $this->assertEquals(5, course_get_format($course['id'])->get_last_section_number()); 1692 $this->assertEquals(0, $courseinfo->newsitems); 1693 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat); 1694 $this->assertEquals(['test' => null], (array)$customfields); 1695 } else if ($course['id'] == $course3['id']) { 1696 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 1697 } else { 1698 throw new moodle_exception('Unexpected shortname'); 1699 } 1700 } 1701 1702 $courses = array($course1); 1703 // Try update course without update capability. 1704 $user = self::getDataGenerator()->create_user(); 1705 $this->setUser($user); 1706 $this->unassignUserCapability('moodle/course:update', $contextid, $roleid); 1707 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1708 $updatedcoursewarnings = core_course_external::update_courses($courses); 1709 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1710 $updatedcoursewarnings); 1711 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1712 1713 // Try update course category without capability. 1714 $this->assignUserCapability('moodle/course:update', $contextid, $roleid); 1715 $this->unassignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1716 $user = self::getDataGenerator()->create_user(); 1717 $this->setUser($user); 1718 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1719 $course1['categoryid'] = $category2->id; 1720 $courses = array($course1); 1721 $updatedcoursewarnings = core_course_external::update_courses($courses); 1722 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1723 $updatedcoursewarnings); 1724 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1725 1726 // Try update course fullname without capability. 1727 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid); 1728 $this->unassignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1729 $user = self::getDataGenerator()->create_user(); 1730 $this->setUser($user); 1731 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1732 $updatedcoursewarnings = core_course_external::update_courses($courses); 1733 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1734 $updatedcoursewarnings); 1735 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1736 $course1['fullname'] = 'Testing fullname without permission'; 1737 $courses = array($course1); 1738 $updatedcoursewarnings = core_course_external::update_courses($courses); 1739 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1740 $updatedcoursewarnings); 1741 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1742 1743 // Try update course shortname without capability. 1744 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid); 1745 $this->unassignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1746 $user = self::getDataGenerator()->create_user(); 1747 $this->setUser($user); 1748 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1749 $updatedcoursewarnings = core_course_external::update_courses($courses); 1750 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1751 $updatedcoursewarnings); 1752 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1753 $course1['shortname'] = 'Testing shortname without permission'; 1754 $courses = array($course1); 1755 $updatedcoursewarnings = core_course_external::update_courses($courses); 1756 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1757 $updatedcoursewarnings); 1758 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1759 1760 // Try update course idnumber without capability. 1761 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid); 1762 $this->unassignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1763 $user = self::getDataGenerator()->create_user(); 1764 $this->setUser($user); 1765 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1766 $updatedcoursewarnings = core_course_external::update_courses($courses); 1767 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1768 $updatedcoursewarnings); 1769 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1770 $course1['idnumber'] = 'NEWIDNUMBER'; 1771 $courses = array($course1); 1772 $updatedcoursewarnings = core_course_external::update_courses($courses); 1773 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1774 $updatedcoursewarnings); 1775 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1776 1777 // Try update course summary without capability. 1778 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid); 1779 $this->unassignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1780 $user = self::getDataGenerator()->create_user(); 1781 $this->setUser($user); 1782 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1783 $updatedcoursewarnings = core_course_external::update_courses($courses); 1784 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1785 $updatedcoursewarnings); 1786 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1787 $course1['summary'] = 'New summary'; 1788 $courses = array($course1); 1789 $updatedcoursewarnings = core_course_external::update_courses($courses); 1790 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1791 $updatedcoursewarnings); 1792 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1793 1794 // Try update course with invalid summary format. 1795 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid); 1796 $user = self::getDataGenerator()->create_user(); 1797 $this->setUser($user); 1798 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1799 $updatedcoursewarnings = core_course_external::update_courses($courses); 1800 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1801 $updatedcoursewarnings); 1802 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1803 $course1['summaryformat'] = 10; 1804 $courses = array($course1); 1805 $updatedcoursewarnings = core_course_external::update_courses($courses); 1806 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1807 $updatedcoursewarnings); 1808 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1809 1810 // Try update course visibility without capability. 1811 $this->unassignUserCapability('moodle/course:visibility', $contextid, $roleid); 1812 $user = self::getDataGenerator()->create_user(); 1813 $this->setUser($user); 1814 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid); 1815 $course1['summaryformat'] = FORMAT_MOODLE; 1816 $courses = array($course1); 1817 $updatedcoursewarnings = core_course_external::update_courses($courses); 1818 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1819 $updatedcoursewarnings); 1820 $this->assertEquals(0, count($updatedcoursewarnings['warnings'])); 1821 $course1['visible'] = 0; 1822 $courses = array($course1); 1823 $updatedcoursewarnings = core_course_external::update_courses($courses); 1824 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(), 1825 $updatedcoursewarnings); 1826 $this->assertEquals(1, count($updatedcoursewarnings['warnings'])); 1827 1828 // Try update course custom fields without capability. 1829 $this->unassignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid); 1830 $user = self::getDataGenerator()->create_user(); 1831 $this->setUser($user); 1832 self::getDataGenerator()->enrol_user($user->id, $course3['id'], $roleid); 1833 1834 $newupdatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'New updated value']; 1835 $course3['customfields'] = [$newupdatedcustomfieldvalue]; 1836 1837 core_course_external::update_courses([$course3]); 1838 1839 // Custom field was not updated. 1840 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course3['id']); 1841 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields); 1842 } 1843 1844 /** 1845 * Test delete course_module. 1846 */ 1847 public function test_delete_modules() { 1848 global $DB; 1849 1850 // Ensure we reset the data after this test. 1851 $this->resetAfterTest(true); 1852 1853 // Create a user. 1854 $user = self::getDataGenerator()->create_user(); 1855 1856 // Set the tests to run as the user. 1857 self::setUser($user); 1858 1859 // Create a course to add the modules. 1860 $course = self::getDataGenerator()->create_course(); 1861 1862 // Create two test modules. 1863 $record = new stdClass(); 1864 $record->course = $course->id; 1865 $module1 = self::getDataGenerator()->create_module('forum', $record); 1866 $module2 = self::getDataGenerator()->create_module('assign', $record); 1867 1868 // Check the forum was correctly created. 1869 $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id))); 1870 1871 // Check the assignment was correctly created. 1872 $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id))); 1873 1874 // Check data exists in the course modules table. 1875 $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 1876 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 1877 1878 // Enrol the user in the course. 1879 $enrol = enrol_get_plugin('manual'); 1880 $enrolinstances = enrol_get_instances($course->id, true); 1881 foreach ($enrolinstances as $courseenrolinstance) { 1882 if ($courseenrolinstance->enrol == "manual") { 1883 $instance = $courseenrolinstance; 1884 break; 1885 } 1886 } 1887 $enrol->enrol_user($instance, $user->id); 1888 1889 // Assign capabilities to delete module 1. 1890 $modcontext = context_module::instance($module1->cmid); 1891 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id); 1892 1893 // Assign capabilities to delete module 2. 1894 $modcontext = context_module::instance($module2->cmid); 1895 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 1896 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole); 1897 1898 // Deleting these module instances. 1899 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 1900 1901 // Check the forum was deleted. 1902 $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id))); 1903 1904 // Check the assignment was deleted. 1905 $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id))); 1906 1907 // Check we retrieve no data in the course modules table. 1908 $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2', 1909 array('module1' => $module1->cmid, 'module2' => $module2->cmid))); 1910 1911 // Call with non-existent course module id and ensure exception thrown. 1912 try { 1913 core_course_external::delete_modules(array('1337')); 1914 $this->fail('Exception expected due to missing course module.'); 1915 } catch (dml_missing_record_exception $e) { 1916 $this->assertEquals('invalidcoursemodule', $e->errorcode); 1917 } 1918 1919 // Create two modules. 1920 $module1 = self::getDataGenerator()->create_module('forum', $record); 1921 $module2 = self::getDataGenerator()->create_module('assign', $record); 1922 1923 // Since these modules were recreated the user will not have capabilities 1924 // to delete them, ensure exception is thrown if they try. 1925 try { 1926 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 1927 $this->fail('Exception expected due to missing capability.'); 1928 } catch (moodle_exception $e) { 1929 $this->assertEquals('nopermissions', $e->errorcode); 1930 } 1931 1932 // Unenrol user from the course. 1933 $enrol->unenrol_user($instance, $user->id); 1934 1935 // Try and delete modules from the course the user was unenrolled in, make sure exception thrown. 1936 try { 1937 core_course_external::delete_modules(array($module1->cmid, $module2->cmid)); 1938 $this->fail('Exception expected due to being unenrolled from the course.'); 1939 } catch (moodle_exception $e) { 1940 $this->assertEquals('requireloginerror', $e->errorcode); 1941 } 1942 } 1943 1944 /** 1945 * Test import_course into an empty course 1946 */ 1947 public function test_import_course_empty() { 1948 global $USER; 1949 1950 $this->resetAfterTest(true); 1951 1952 $course1 = self::getDataGenerator()->create_course(); 1953 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id, 'name' => 'Forum test')); 1954 $page = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 'name' => 'Page test')); 1955 1956 $course2 = self::getDataGenerator()->create_course(); 1957 1958 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 1959 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 1960 1961 // Verify the state of the courses before we do the import. 1962 $this->assertCount(2, $course1cms); 1963 $this->assertEmpty($course2cms); 1964 1965 // Setup the user to run the operation (ugly hack because validate_context() will 1966 // fail as the email is not set by $this->setAdminUser()). 1967 $this->setAdminUser(); 1968 $USER->email = 'emailtopass@example.com'; 1969 1970 // Import from course1 to course2. 1971 core_course_external::import_course($course1->id, $course2->id, 0); 1972 1973 // Verify that now we have two modules in both courses. 1974 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 1975 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 1976 $this->assertCount(2, $course1cms); 1977 $this->assertCount(2, $course2cms); 1978 1979 // Verify that the names transfered across correctly. 1980 foreach ($course2cms as $cm) { 1981 if ($cm->modname === 'page') { 1982 $this->assertEquals($cm->name, $page->name); 1983 } else if ($cm->modname === 'forum') { 1984 $this->assertEquals($cm->name, $forum->name); 1985 } else { 1986 $this->fail('Unknown CM found.'); 1987 } 1988 } 1989 } 1990 1991 /** 1992 * Test import_course into an filled course 1993 */ 1994 public function test_import_course_filled() { 1995 global $USER; 1996 1997 $this->resetAfterTest(true); 1998 1999 // Add forum and page to course1. 2000 $course1 = self::getDataGenerator()->create_course(); 2001 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2002 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2003 2004 // Add quiz to course 2. 2005 $course2 = self::getDataGenerator()->create_course(); 2006 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2007 2008 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2009 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2010 2011 // Verify the state of the courses before we do the import. 2012 $this->assertCount(2, $course1cms); 2013 $this->assertCount(1, $course2cms); 2014 2015 // Setup the user to run the operation (ugly hack because validate_context() will 2016 // fail as the email is not set by $this->setAdminUser()). 2017 $this->setAdminUser(); 2018 $USER->email = 'emailtopass@example.com'; 2019 2020 // Import from course1 to course2 without deleting content. 2021 core_course_external::import_course($course1->id, $course2->id, 0); 2022 2023 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2024 2025 // Verify that now we have three modules in course2. 2026 $this->assertCount(3, $course2cms); 2027 2028 // Verify that the names transfered across correctly. 2029 foreach ($course2cms as $cm) { 2030 if ($cm->modname === 'page') { 2031 $this->assertEquals($cm->name, $page->name); 2032 } else if ($cm->modname === 'forum') { 2033 $this->assertEquals($cm->name, $forum->name); 2034 } else if ($cm->modname === 'quiz') { 2035 $this->assertEquals($cm->name, $quiz->name); 2036 } else { 2037 $this->fail('Unknown CM found.'); 2038 } 2039 } 2040 } 2041 2042 /** 2043 * Test import_course with only blocks set to backup 2044 */ 2045 public function test_import_course_blocksonly() { 2046 global $USER, $DB; 2047 2048 $this->resetAfterTest(true); 2049 2050 // Add forum and page to course1. 2051 $course1 = self::getDataGenerator()->create_course(); 2052 $course1ctx = context_course::instance($course1->id); 2053 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2054 $block = $this->getDataGenerator()->create_block('online_users', array('parentcontextid' => $course1ctx->id)); 2055 2056 $course2 = self::getDataGenerator()->create_course(); 2057 $course2ctx = context_course::instance($course2->id); 2058 $initialblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2059 $initialcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2060 2061 // Setup the user to run the operation (ugly hack because validate_context() will 2062 // fail as the email is not set by $this->setAdminUser()). 2063 $this->setAdminUser(); 2064 $USER->email = 'emailtopass@example.com'; 2065 2066 // Import from course1 to course2 without deleting content, but excluding 2067 // activities. 2068 $options = array( 2069 array('name' => 'activities', 'value' => 0), 2070 array('name' => 'blocks', 'value' => 1), 2071 array('name' => 'filters', 'value' => 0), 2072 ); 2073 2074 core_course_external::import_course($course1->id, $course2->id, 0, $options); 2075 2076 $newcmcount = count(get_fast_modinfo($course2->id)->get_cms()); 2077 $newblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id)); 2078 // Check that course modules haven't changed, but that blocks have. 2079 $this->assertEquals($initialcmcount, $newcmcount); 2080 $this->assertEquals(($initialblockcount + 1), $newblockcount); 2081 } 2082 2083 /** 2084 * Test import_course into an filled course, deleting content. 2085 */ 2086 public function test_import_course_deletecontent() { 2087 global $USER; 2088 $this->resetAfterTest(true); 2089 2090 // Add forum and page to course1. 2091 $course1 = self::getDataGenerator()->create_course(); 2092 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test')); 2093 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test')); 2094 2095 // Add quiz to course 2. 2096 $course2 = self::getDataGenerator()->create_course(); 2097 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test')); 2098 2099 $course1cms = get_fast_modinfo($course1->id)->get_cms(); 2100 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2101 2102 // Verify the state of the courses before we do the import. 2103 $this->assertCount(2, $course1cms); 2104 $this->assertCount(1, $course2cms); 2105 2106 // Setup the user to run the operation (ugly hack because validate_context() will 2107 // fail as the email is not set by $this->setAdminUser()). 2108 $this->setAdminUser(); 2109 $USER->email = 'emailtopass@example.com'; 2110 2111 // Import from course1 to course2, deleting content. 2112 core_course_external::import_course($course1->id, $course2->id, 1); 2113 2114 $course2cms = get_fast_modinfo($course2->id)->get_cms(); 2115 2116 // Verify that now we have two modules in course2. 2117 $this->assertCount(2, $course2cms); 2118 2119 // Verify that the course only contains the imported modules. 2120 foreach ($course2cms as $cm) { 2121 if ($cm->modname === 'page') { 2122 $this->assertEquals($cm->name, $page->name); 2123 } else if ($cm->modname === 'forum') { 2124 $this->assertEquals($cm->name, $forum->name); 2125 } else { 2126 $this->fail('Unknown CM found: '.$cm->name); 2127 } 2128 } 2129 } 2130 2131 /** 2132 * Ensure import_course handles incorrect deletecontent option correctly. 2133 */ 2134 public function test_import_course_invalid_deletecontent_option() { 2135 $this->resetAfterTest(true); 2136 2137 $course1 = self::getDataGenerator()->create_course(); 2138 $course2 = self::getDataGenerator()->create_course(); 2139 2140 $this->expectException('moodle_exception'); 2141 $this->expectExceptionMessage(get_string('invalidextparam', 'webservice', -1)); 2142 // Import from course1 to course2, with invalid option 2143 core_course_external::import_course($course1->id, $course2->id, -1);; 2144 } 2145 2146 /** 2147 * Test view_course function 2148 */ 2149 public function test_view_course() { 2150 2151 $this->resetAfterTest(); 2152 2153 // Course without sections. 2154 $course = $this->getDataGenerator()->create_course(array('numsections' => 5), array('createsections' => true)); 2155 $this->setAdminUser(); 2156 2157 // Redirect events to the sink, so we can recover them later. 2158 $sink = $this->redirectEvents(); 2159 2160 $result = core_course_external::view_course($course->id, 1); 2161 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2162 $events = $sink->get_events(); 2163 $event = reset($events); 2164 2165 // Check the event details are correct. 2166 $this->assertInstanceOf('\core\event\course_viewed', $event); 2167 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2168 $this->assertEquals(1, $event->other['coursesectionnumber']); 2169 2170 $result = core_course_external::view_course($course->id); 2171 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result); 2172 $events = $sink->get_events(); 2173 $event = array_pop($events); 2174 $sink->close(); 2175 2176 // Check the event details are correct. 2177 $this->assertInstanceOf('\core\event\course_viewed', $event); 2178 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2179 $this->assertEmpty($event->other); 2180 2181 } 2182 2183 /** 2184 * Test get_course_module 2185 */ 2186 public function test_get_course_module() { 2187 global $DB; 2188 2189 $this->resetAfterTest(true); 2190 2191 $this->setAdminUser(); 2192 $course = self::getDataGenerator()->create_course(); 2193 $record = array( 2194 'course' => $course->id, 2195 'name' => 'First Assignment' 2196 ); 2197 $options = array( 2198 'idnumber' => 'ABC', 2199 'visible' => 0 2200 ); 2201 // Hidden activity. 2202 $assign = self::getDataGenerator()->create_module('assign', $record, $options); 2203 2204 $outcomescale = 'Distinction, Very Good, Good, Pass, Fail'; 2205 2206 // Insert a custom grade scale to be used by an outcome. 2207 $gradescale = new grade_scale(); 2208 $gradescale->name = 'gettcoursemodulescale'; 2209 $gradescale->courseid = $course->id; 2210 $gradescale->userid = 0; 2211 $gradescale->scale = $outcomescale; 2212 $gradescale->description = 'This scale is used to mark standard assignments.'; 2213 $gradescale->insert(); 2214 2215 // Insert an outcome. 2216 $data = new stdClass(); 2217 $data->courseid = $course->id; 2218 $data->fullname = 'Team work'; 2219 $data->shortname = 'Team work'; 2220 $data->scaleid = $gradescale->id; 2221 $outcome = new grade_outcome($data, false); 2222 $outcome->insert(); 2223 2224 $outcomegradeitem = new grade_item(); 2225 $outcomegradeitem->itemname = $outcome->shortname; 2226 $outcomegradeitem->itemtype = 'mod'; 2227 $outcomegradeitem->itemmodule = 'assign'; 2228 $outcomegradeitem->iteminstance = $assign->id; 2229 $outcomegradeitem->outcomeid = $outcome->id; 2230 $outcomegradeitem->cmid = 0; 2231 $outcomegradeitem->courseid = $course->id; 2232 $outcomegradeitem->aggregationcoef = 0; 2233 $outcomegradeitem->itemnumber = 1000; // Outcomes start at 1000. 2234 $outcomegradeitem->gradetype = GRADE_TYPE_SCALE; 2235 $outcomegradeitem->scaleid = $outcome->scaleid; 2236 $outcomegradeitem->insert(); 2237 2238 $assignmentgradeitem = grade_item::fetch( 2239 array( 2240 'itemtype' => 'mod', 2241 'itemmodule' => 'assign', 2242 'iteminstance' => $assign->id, 2243 'itemnumber' => 0, 2244 'courseid' => $course->id 2245 ) 2246 ); 2247 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid); 2248 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder); 2249 2250 // Test admin user can see the complete hidden activity. 2251 $result = core_course_external::get_course_module($assign->cmid); 2252 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2253 2254 $this->assertCount(0, $result['warnings']); 2255 // Test we retrieve all the fields. 2256 $this->assertCount(28, $result['cm']); 2257 $this->assertEquals($record['name'], $result['cm']['name']); 2258 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2259 $this->assertEquals(100, $result['cm']['grade']); 2260 $this->assertEquals(0.0, $result['cm']['gradepass']); 2261 $this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']); 2262 $this->assertEmpty($result['cm']['advancedgrading'][0]['method']); 2263 $this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']); 2264 2265 $student = $this->getDataGenerator()->create_user(); 2266 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2267 2268 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2269 $this->setUser($student); 2270 2271 // The user shouldn't be able to see the activity. 2272 try { 2273 core_course_external::get_course_module($assign->cmid); 2274 $this->fail('Exception expected due to invalid permissions.'); 2275 } catch (moodle_exception $e) { 2276 $this->assertEquals('requireloginerror', $e->errorcode); 2277 } 2278 2279 // Make module visible. 2280 set_coursemodule_visible($assign->cmid, 1); 2281 2282 // Test student user. 2283 $result = core_course_external::get_course_module($assign->cmid); 2284 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result); 2285 2286 $this->assertCount(0, $result['warnings']); 2287 // Test we retrieve only the few files we can see. 2288 $this->assertCount(11, $result['cm']); 2289 $this->assertEquals($assign->cmid, $result['cm']['id']); 2290 $this->assertEquals($course->id, $result['cm']['course']); 2291 $this->assertEquals('assign', $result['cm']['modname']); 2292 $this->assertEquals($assign->id, $result['cm']['instance']); 2293 2294 } 2295 2296 /** 2297 * Test get_course_module_by_instance 2298 */ 2299 public function test_get_course_module_by_instance() { 2300 global $DB; 2301 2302 $this->resetAfterTest(true); 2303 2304 $this->setAdminUser(); 2305 $course = self::getDataGenerator()->create_course(); 2306 $record = array( 2307 'course' => $course->id, 2308 'name' => 'First quiz', 2309 'grade' => 90.00 2310 ); 2311 $options = array( 2312 'idnumber' => 'ABC', 2313 'visible' => 0 2314 ); 2315 // Hidden activity. 2316 $quiz = self::getDataGenerator()->create_module('quiz', $record, $options); 2317 2318 // Test admin user can see the complete hidden activity. 2319 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2320 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2321 2322 $this->assertCount(0, $result['warnings']); 2323 // Test we retrieve all the fields. 2324 $this->assertCount(26, $result['cm']); 2325 $this->assertEquals($record['name'], $result['cm']['name']); 2326 $this->assertEquals($record['grade'], $result['cm']['grade']); 2327 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']); 2328 2329 $student = $this->getDataGenerator()->create_user(); 2330 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2331 2332 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2333 $this->setUser($student); 2334 2335 // The user shouldn't be able to see the activity. 2336 try { 2337 core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2338 $this->fail('Exception expected due to invalid permissions.'); 2339 } catch (moodle_exception $e) { 2340 $this->assertEquals('requireloginerror', $e->errorcode); 2341 } 2342 2343 // Make module visible. 2344 set_coursemodule_visible($quiz->cmid, 1); 2345 2346 // Test student user. 2347 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id); 2348 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result); 2349 2350 $this->assertCount(0, $result['warnings']); 2351 // Test we retrieve only the few files we can see. 2352 $this->assertCount(11, $result['cm']); 2353 $this->assertEquals($quiz->cmid, $result['cm']['id']); 2354 $this->assertEquals($course->id, $result['cm']['course']); 2355 $this->assertEquals('quiz', $result['cm']['modname']); 2356 $this->assertEquals($quiz->id, $result['cm']['instance']); 2357 2358 // Try with an invalid module name. 2359 try { 2360 core_course_external::get_course_module_by_instance('abc', $quiz->id); 2361 $this->fail('Exception expected due to invalid module name.'); 2362 } catch (dml_read_exception $e) { 2363 $this->assertEquals('dmlreadexception', $e->errorcode); 2364 } 2365 2366 } 2367 2368 /** 2369 * Test get_user_navigation_options 2370 */ 2371 public function test_get_user_navigation_options() { 2372 global $USER; 2373 2374 $this->resetAfterTest(); 2375 $course1 = self::getDataGenerator()->create_course(); 2376 $course2 = self::getDataGenerator()->create_course(); 2377 2378 // Create a viewer user. 2379 $viewer = self::getDataGenerator()->create_user(); 2380 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2381 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2382 2383 $this->setUser($viewer->id); 2384 $courses = array($course1->id , $course2->id, SITEID); 2385 2386 $result = core_course_external::get_user_navigation_options($courses); 2387 $result = external_api::clean_returnvalue(core_course_external::get_user_navigation_options_returns(), $result); 2388 2389 $this->assertCount(0, $result['warnings']); 2390 $this->assertCount(3, $result['courses']); 2391 2392 foreach ($result['courses'] as $course) { 2393 $navoptions = new stdClass; 2394 foreach ($course['options'] as $option) { 2395 $navoptions->{$option['name']} = $option['available']; 2396 } 2397 $this->assertCount(9, $course['options']); 2398 if ($course['id'] == SITEID) { 2399 $this->assertTrue($navoptions->blogs); 2400 $this->assertFalse($navoptions->notes); 2401 $this->assertFalse($navoptions->participants); 2402 $this->assertTrue($navoptions->badges); 2403 $this->assertTrue($navoptions->tags); 2404 $this->assertFalse($navoptions->grades); 2405 $this->assertFalse($navoptions->search); 2406 $this->assertTrue($navoptions->calendar); 2407 $this->assertTrue($navoptions->competencies); 2408 } else { 2409 $this->assertTrue($navoptions->blogs); 2410 $this->assertFalse($navoptions->notes); 2411 $this->assertTrue($navoptions->participants); 2412 $this->assertTrue($navoptions->badges); 2413 $this->assertFalse($navoptions->tags); 2414 $this->assertTrue($navoptions->grades); 2415 $this->assertFalse($navoptions->search); 2416 $this->assertFalse($navoptions->calendar); 2417 $this->assertTrue($navoptions->competencies); 2418 } 2419 } 2420 } 2421 2422 /** 2423 * Test get_user_administration_options 2424 */ 2425 public function test_get_user_administration_options() { 2426 global $USER; 2427 2428 $this->resetAfterTest(); 2429 $course1 = self::getDataGenerator()->create_course(); 2430 $course2 = self::getDataGenerator()->create_course(); 2431 2432 // Create a viewer user. 2433 $viewer = self::getDataGenerator()->create_user(); 2434 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id); 2435 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id); 2436 2437 $this->setUser($viewer->id); 2438 $courses = array($course1->id , $course2->id, SITEID); 2439 2440 $result = core_course_external::get_user_administration_options($courses); 2441 $result = external_api::clean_returnvalue(core_course_external::get_user_administration_options_returns(), $result); 2442 2443 $this->assertCount(0, $result['warnings']); 2444 $this->assertCount(3, $result['courses']); 2445 2446 foreach ($result['courses'] as $course) { 2447 $adminoptions = new stdClass; 2448 foreach ($course['options'] as $option) { 2449 $adminoptions->{$option['name']} = $option['available']; 2450 } 2451 if ($course['id'] == SITEID) { 2452 $this->assertCount(17, $course['options']); 2453 $this->assertFalse($adminoptions->update); 2454 $this->assertFalse($adminoptions->filters); 2455 $this->assertFalse($adminoptions->reports); 2456 $this->assertFalse($adminoptions->backup); 2457 $this->assertFalse($adminoptions->restore); 2458 $this->assertFalse($adminoptions->files); 2459 $this->assertFalse(!isset($adminoptions->tags)); 2460 $this->assertFalse($adminoptions->gradebook); 2461 $this->assertFalse($adminoptions->outcomes); 2462 $this->assertFalse($adminoptions->badges); 2463 $this->assertFalse($adminoptions->import); 2464 $this->assertFalse($adminoptions->reset); 2465 $this->assertFalse($adminoptions->roles); 2466 $this->assertFalse($adminoptions->editcompletion); 2467 $this->assertFalse($adminoptions->copy); 2468 } else { 2469 $this->assertCount(15, $course['options']); 2470 $this->assertFalse($adminoptions->update); 2471 $this->assertFalse($adminoptions->filters); 2472 $this->assertFalse($adminoptions->reports); 2473 $this->assertFalse($adminoptions->backup); 2474 $this->assertFalse($adminoptions->restore); 2475 $this->assertFalse($adminoptions->files); 2476 $this->assertFalse($adminoptions->tags); 2477 $this->assertFalse($adminoptions->gradebook); 2478 $this->assertFalse($adminoptions->outcomes); 2479 $this->assertTrue($adminoptions->badges); 2480 $this->assertFalse($adminoptions->import); 2481 $this->assertFalse($adminoptions->reset); 2482 $this->assertFalse($adminoptions->roles); 2483 $this->assertFalse($adminoptions->editcompletion); 2484 $this->assertFalse($adminoptions->copy); 2485 } 2486 } 2487 } 2488 2489 /** 2490 * Test get_courses_by_fields 2491 */ 2492 public function test_get_courses_by_field() { 2493 global $DB; 2494 $this->resetAfterTest(true); 2495 2496 $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1')); 2497 $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id)); 2498 $course1 = self::getDataGenerator()->create_course( 2499 array('category' => $category1->id, 'shortname' => 'c1', 'format' => 'topics')); 2500 2501 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']); 2502 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text', 2503 'categoryid' => $fieldcategory->get('id')]; 2504 $field = self::getDataGenerator()->create_custom_field($customfield); 2505 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; 2506 $course2 = self::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id, 'idnumber' => 'i2', 'customfields' => [$customfieldvalue])); 2507 2508 $student1 = self::getDataGenerator()->create_user(); 2509 $user1 = self::getDataGenerator()->create_user(); 2510 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2511 self::getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id); 2512 self::getDataGenerator()->enrol_user($student1->id, $course2->id, $studentrole->id); 2513 2514 self::setAdminUser(); 2515 // As admins, we should be able to retrieve everything. 2516 $result = core_course_external::get_courses_by_field(); 2517 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2518 $this->assertCount(3, $result['courses']); 2519 // Expect to receive all the fields. 2520 $this->assertCount(38, $result['courses'][0]); 2521 $this->assertCount(39, $result['courses'][1]); // One more field because is not the site course. 2522 $this->assertCount(39, $result['courses'][2]); // One more field because is not the site course. 2523 2524 $result = core_course_external::get_courses_by_field('id', $course1->id); 2525 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2526 $this->assertCount(1, $result['courses']); 2527 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2528 // Expect to receive all the fields. 2529 $this->assertCount(39, $result['courses'][0]); 2530 // Check default values for course format topics. 2531 $this->assertCount(2, $result['courses'][0]['courseformatoptions']); 2532 foreach ($result['courses'][0]['courseformatoptions'] as $option) { 2533 if ($option['name'] == 'hiddensections') { 2534 $this->assertEquals(0, $option['value']); 2535 } else { 2536 $this->assertEquals('coursedisplay', $option['name']); 2537 $this->assertEquals(0, $option['value']); 2538 } 2539 } 2540 2541 $result = core_course_external::get_courses_by_field('id', $course2->id); 2542 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2543 $this->assertCount(1, $result['courses']); 2544 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2545 // Check custom fields properly returned. 2546 unset($customfield['categoryid']); 2547 $this->assertEquals([array_merge($customfield, $customfieldvalue)], $result['courses'][0]['customfields']); 2548 2549 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2550 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2551 $this->assertCount(2, $result['courses']); 2552 2553 // Check default filters. 2554 $this->assertCount(6, $result['courses'][0]['filters']); 2555 $this->assertCount(6, $result['courses'][1]['filters']); 2556 2557 $result = core_course_external::get_courses_by_field('category', $category1->id); 2558 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2559 $this->assertCount(1, $result['courses']); 2560 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2561 $this->assertEquals('Cat 1', $result['courses'][0]['categoryname']); 2562 2563 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2564 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2565 $this->assertCount(1, $result['courses']); 2566 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2567 2568 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2569 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2570 $this->assertCount(1, $result['courses']); 2571 $this->assertEquals($course2->id, $result['courses'][0]['id']); 2572 2573 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2574 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2575 $this->assertCount(0, $result['courses']); 2576 2577 // Change filter value. 2578 filter_set_local_state('mediaplugin', context_course::instance($course1->id)->id, TEXTFILTER_OFF); 2579 2580 self::setUser($student1); 2581 // All visible courses (including front page) for normal student. 2582 $result = core_course_external::get_courses_by_field(); 2583 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2584 $this->assertCount(2, $result['courses']); 2585 $this->assertCount(31, $result['courses'][0]); 2586 $this->assertCount(32, $result['courses'][1]); // One field more (course format options), not present in site course. 2587 2588 $result = core_course_external::get_courses_by_field('id', $course1->id); 2589 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2590 $this->assertCount(1, $result['courses']); 2591 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2592 // Expect to receive all the files that a student can see. 2593 $this->assertCount(32, $result['courses'][0]); 2594 2595 // Check default filters. 2596 $filters = $result['courses'][0]['filters']; 2597 $this->assertCount(6, $filters); 2598 $found = false; 2599 foreach ($filters as $filter) { 2600 if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF) { 2601 $found = true; 2602 } 2603 } 2604 $this->assertTrue($found); 2605 2606 // Course 2 is not visible. 2607 $result = core_course_external::get_courses_by_field('id', $course2->id); 2608 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2609 $this->assertCount(0, $result['courses']); 2610 2611 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2612 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2613 $this->assertCount(1, $result['courses']); 2614 2615 $result = core_course_external::get_courses_by_field('category', $category1->id); 2616 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2617 $this->assertCount(1, $result['courses']); 2618 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2619 2620 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2621 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2622 $this->assertCount(1, $result['courses']); 2623 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2624 2625 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2626 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2627 $this->assertCount(0, $result['courses']); 2628 2629 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2630 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2631 $this->assertCount(0, $result['courses']); 2632 2633 self::setUser($user1); 2634 // All visible courses (including front page) for authenticated user. 2635 $result = core_course_external::get_courses_by_field(); 2636 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2637 $this->assertCount(2, $result['courses']); 2638 $this->assertCount(31, $result['courses'][0]); // Site course. 2639 $this->assertCount(14, $result['courses'][1]); // Only public information, not enrolled. 2640 2641 $result = core_course_external::get_courses_by_field('id', $course1->id); 2642 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2643 $this->assertCount(1, $result['courses']); 2644 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2645 // Expect to receive all the files that a authenticated can see. 2646 $this->assertCount(14, $result['courses'][0]); 2647 2648 // Course 2 is not visible. 2649 $result = core_course_external::get_courses_by_field('id', $course2->id); 2650 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2651 $this->assertCount(0, $result['courses']); 2652 2653 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); 2654 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2655 $this->assertCount(1, $result['courses']); 2656 2657 $result = core_course_external::get_courses_by_field('category', $category1->id); 2658 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2659 $this->assertCount(1, $result['courses']); 2660 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2661 2662 $result = core_course_external::get_courses_by_field('shortname', 'c1'); 2663 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2664 $this->assertCount(1, $result['courses']); 2665 $this->assertEquals($course1->id, $result['courses'][0]['id']); 2666 2667 $result = core_course_external::get_courses_by_field('idnumber', 'i2'); 2668 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2669 $this->assertCount(0, $result['courses']); 2670 2671 $result = core_course_external::get_courses_by_field('idnumber', 'x'); 2672 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2673 $this->assertCount(0, $result['courses']); 2674 } 2675 2676 public function test_get_courses_by_field_invalid_field() { 2677 $this->expectException('invalid_parameter_exception'); 2678 $result = core_course_external::get_courses_by_field('zyx', 'x'); 2679 } 2680 2681 public function test_get_courses_by_field_invalid_courses() { 2682 $result = core_course_external::get_courses_by_field('id', '-1'); 2683 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2684 $this->assertCount(0, $result['courses']); 2685 } 2686 2687 /** 2688 * Test get_courses_by_field_invalid_theme_and_lang 2689 */ 2690 public function test_get_courses_by_field_invalid_theme_and_lang() { 2691 $this->resetAfterTest(true); 2692 $this->setAdminUser(); 2693 2694 $course = self::getDataGenerator()->create_course(array('theme' => 'kkt', 'lang' => 'kkl')); 2695 $result = core_course_external::get_courses_by_field('id', $course->id); 2696 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); 2697 $this->assertEmpty($result['courses']['0']['theme']); 2698 $this->assertEmpty($result['courses']['0']['lang']); 2699 } 2700 2701 2702 public function test_check_updates() { 2703 global $DB; 2704 $this->resetAfterTest(true); 2705 $this->setAdminUser(); 2706 2707 // Create different types of activities. 2708 $course = self::getDataGenerator()->create_course(); 2709 $tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz', 2710 'resource', 'scorm', 'survey', 'url', 'wiki'); 2711 2712 $modules = array(); 2713 foreach ($tocreate as $modname) { 2714 $modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id)); 2715 $modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid); 2716 $modules[$modname]['context'] = context_module::instance($modules[$modname]['instance']->cmid); 2717 } 2718 2719 $student = self::getDataGenerator()->create_user(); 2720 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2721 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); 2722 $this->setUser($student); 2723 2724 $since = time(); 2725 $this->waitForSecond(); 2726 $params = array(); 2727 foreach ($modules as $modname => $data) { 2728 $params[$data['cm']->id] = array( 2729 'contextlevel' => 'module', 2730 'id' => $data['cm']->id, 2731 'since' => $since 2732 ); 2733 } 2734 2735 // Check there is nothing updated because modules are fresh new. 2736 $result = core_course_external::check_updates($course->id, $params); 2737 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2738 $this->assertCount(0, $result['instances']); 2739 $this->assertCount(0, $result['warnings']); 2740 2741 // Test with get_updates_since the same data. 2742 $result = core_course_external::get_updates_since($course->id, $since); 2743 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 2744 $this->assertCount(0, $result['instances']); 2745 $this->assertCount(0, $result['warnings']); 2746 2747 // Update a module after a second. 2748 $this->waitForSecond(); 2749 set_coursemodule_name($modules['forum']['cm']->id, 'New forum name'); 2750 2751 $found = false; 2752 $result = core_course_external::check_updates($course->id, $params); 2753 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2754 $this->assertCount(1, $result['instances']); 2755 $this->assertCount(0, $result['warnings']); 2756 foreach ($result['instances'] as $module) { 2757 foreach ($module['updates'] as $update) { 2758 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 2759 $found = true; 2760 } 2761 } 2762 } 2763 $this->assertTrue($found); 2764 2765 // Test with get_updates_since the same data. 2766 $result = core_course_external::get_updates_since($course->id, $since); 2767 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result); 2768 $this->assertCount(1, $result['instances']); 2769 $this->assertCount(0, $result['warnings']); 2770 $found = false; 2771 $this->assertCount(1, $result['instances']); 2772 $this->assertCount(0, $result['warnings']); 2773 foreach ($result['instances'] as $module) { 2774 foreach ($module['updates'] as $update) { 2775 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') { 2776 $found = true; 2777 } 2778 } 2779 } 2780 $this->assertTrue($found); 2781 2782 // Do not retrieve the configuration field. 2783 $filter = array('files'); 2784 $found = false; 2785 $result = core_course_external::check_updates($course->id, $params, $filter); 2786 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2787 $this->assertCount(0, $result['instances']); 2788 $this->assertCount(0, $result['warnings']); 2789 $this->assertFalse($found); 2790 2791 // Add invalid cmid. 2792 $params[] = array( 2793 'contextlevel' => 'module', 2794 'id' => -2, 2795 'since' => $since 2796 ); 2797 $result = core_course_external::check_updates($course->id, $params); 2798 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result); 2799 $this->assertCount(1, $result['warnings']); 2800 $this->assertEquals(-2, $result['warnings'][0]['itemid']); 2801 } 2802 2803 /** 2804 * Test cases for the get_enrolled_courses_by_timeline_classification test. 2805 */ 2806 public function get_get_enrolled_courses_by_timeline_classification_test_cases():array { 2807 $now = time(); 2808 $day = 86400; 2809 2810 $coursedata = [ 2811 [ 2812 'shortname' => 'apast', 2813 'startdate' => $now - ($day * 2), 2814 'enddate' => $now - $day 2815 ], 2816 [ 2817 'shortname' => 'bpast', 2818 'startdate' => $now - ($day * 2), 2819 'enddate' => $now - $day 2820 ], 2821 [ 2822 'shortname' => 'cpast', 2823 'startdate' => $now - ($day * 2), 2824 'enddate' => $now - $day 2825 ], 2826 [ 2827 'shortname' => 'dpast', 2828 'startdate' => $now - ($day * 2), 2829 'enddate' => $now - $day 2830 ], 2831 [ 2832 'shortname' => 'epast', 2833 'startdate' => $now - ($day * 2), 2834 'enddate' => $now - $day 2835 ], 2836 [ 2837 'shortname' => 'ainprogress', 2838 'startdate' => $now - $day, 2839 'enddate' => $now + $day 2840 ], 2841 [ 2842 'shortname' => 'binprogress', 2843 'startdate' => $now - $day, 2844 'enddate' => $now + $day 2845 ], 2846 [ 2847 'shortname' => 'cinprogress', 2848 'startdate' => $now - $day, 2849 'enddate' => $now + $day 2850 ], 2851 [ 2852 'shortname' => 'dinprogress', 2853 'startdate' => $now - $day, 2854 'enddate' => $now + $day 2855 ], 2856 [ 2857 'shortname' => 'einprogress', 2858 'startdate' => $now - $day, 2859 'enddate' => $now + $day 2860 ], 2861 [ 2862 'shortname' => 'afuture', 2863 'startdate' => $now + $day 2864 ], 2865 [ 2866 'shortname' => 'bfuture', 2867 'startdate' => $now + $day 2868 ], 2869 [ 2870 'shortname' => 'cfuture', 2871 'startdate' => $now + $day 2872 ], 2873 [ 2874 'shortname' => 'dfuture', 2875 'startdate' => $now + $day 2876 ], 2877 [ 2878 'shortname' => 'efuture', 2879 'startdate' => $now + $day 2880 ] 2881 ]; 2882 2883 // Raw enrolled courses result set should be returned in this order: 2884 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast, 2885 // dfuture, dinprogress, dpast, efuture, einprogress, epast 2886 // 2887 // By classification the offset values for each record should be: 2888 // COURSE_TIMELINE_FUTURE 2889 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture) 2890 // COURSE_TIMELINE_INPROGRESS 2891 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress) 2892 // COURSE_TIMELINE_PAST 2893 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast). 2894 // 2895 // NOTE: The offset applies to the unfiltered full set of courses before the classification 2896 // filtering is done. 2897 // E.g. In our example if an offset of 2 is given then it would mean the first 2898 // two courses (afuture, ainprogress) are ignored. 2899 return [ 2900 'empty set' => [ 2901 'coursedata' => [], 2902 'classification' => 'future', 2903 'limit' => 2, 2904 'offset' => 0, 2905 'sort' => 'shortname ASC', 2906 'expectedcourses' => [], 2907 'expectednextoffset' => 0 2908 ], 2909 // COURSE_TIMELINE_FUTURE. 2910 'future not limit no offset' => [ 2911 'coursedata' => $coursedata, 2912 'classification' => 'future', 2913 'limit' => 0, 2914 'offset' => 0, 2915 'sort' => 'shortname ASC', 2916 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 2917 'expectednextoffset' => 15 2918 ], 2919 'future no offset' => [ 2920 'coursedata' => $coursedata, 2921 'classification' => 'future', 2922 'limit' => 2, 2923 'offset' => 0, 2924 'sort' => 'shortname ASC', 2925 'expectedcourses' => ['afuture', 'bfuture'], 2926 'expectednextoffset' => 4 2927 ], 2928 'future offset' => [ 2929 'coursedata' => $coursedata, 2930 'classification' => 'future', 2931 'limit' => 2, 2932 'offset' => 2, 2933 'sort' => 'shortname ASC', 2934 'expectedcourses' => ['bfuture', 'cfuture'], 2935 'expectednextoffset' => 7 2936 ], 2937 'future exact limit' => [ 2938 'coursedata' => $coursedata, 2939 'classification' => 'future', 2940 'limit' => 5, 2941 'offset' => 0, 2942 'sort' => 'shortname ASC', 2943 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 2944 'expectednextoffset' => 13 2945 ], 2946 'future limit less results' => [ 2947 'coursedata' => $coursedata, 2948 'classification' => 'future', 2949 'limit' => 10, 2950 'offset' => 0, 2951 'sort' => 'shortname ASC', 2952 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 2953 'expectednextoffset' => 15 2954 ], 2955 'future limit less results with offset' => [ 2956 'coursedata' => $coursedata, 2957 'classification' => 'future', 2958 'limit' => 10, 2959 'offset' => 5, 2960 'sort' => 'shortname ASC', 2961 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'], 2962 'expectednextoffset' => 15 2963 ], 2964 'all no limit or offset' => [ 2965 'coursedata' => $coursedata, 2966 'classification' => 'all', 2967 'limit' => 0, 2968 'offset' => 0, 2969 'sort' => 'shortname ASC', 2970 'expectedcourses' => [ 2971 'afuture', 2972 'ainprogress', 2973 'apast', 2974 'bfuture', 2975 'binprogress', 2976 'bpast', 2977 'cfuture', 2978 'cinprogress', 2979 'cpast', 2980 'dfuture', 2981 'dinprogress', 2982 'dpast', 2983 'efuture', 2984 'einprogress', 2985 'epast' 2986 ], 2987 'expectednextoffset' => 15 2988 ], 2989 'all limit no offset' => [ 2990 'coursedata' => $coursedata, 2991 'classification' => 'all', 2992 'limit' => 5, 2993 'offset' => 0, 2994 'sort' => 'shortname ASC', 2995 'expectedcourses' => [ 2996 'afuture', 2997 'ainprogress', 2998 'apast', 2999 'bfuture', 3000 'binprogress' 3001 ], 3002 'expectednextoffset' => 5 3003 ], 3004 'all limit and offset' => [ 3005 'coursedata' => $coursedata, 3006 'classification' => 'all', 3007 'limit' => 5, 3008 'offset' => 5, 3009 'sort' => 'shortname ASC', 3010 'expectedcourses' => [ 3011 'bpast', 3012 'cfuture', 3013 'cinprogress', 3014 'cpast', 3015 'dfuture' 3016 ], 3017 'expectednextoffset' => 10 3018 ], 3019 'all offset past result set' => [ 3020 'coursedata' => $coursedata, 3021 'classification' => 'all', 3022 'limit' => 5, 3023 'offset' => 50, 3024 'sort' => 'shortname ASC', 3025 'expectedcourses' => [], 3026 'expectednextoffset' => 50 3027 ], 3028 'all limit and offset with sort ul.timeaccess desc' => [ 3029 'coursedata' => $coursedata, 3030 'classification' => 'inprogress', 3031 'limit' => 0, 3032 'offset' => 0, 3033 'sort' => 'ul.timeaccess desc', 3034 'expectedcourses' => [ 3035 'ainprogress', 3036 'binprogress', 3037 'cinprogress', 3038 'dinprogress', 3039 'einprogress' 3040 ], 3041 'expectednextoffset' => 15 3042 ], 3043 'all limit and offset with sort sql injection for sort or 1==1' => [ 3044 'coursedata' => $coursedata, 3045 'classification' => 'all', 3046 'limit' => 5, 3047 'offset' => 5, 3048 'sort' => 'ul.timeaccess desc or 1==1', 3049 'expectedcourses' => [], 3050 'expectednextoffset' => 0, 3051 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3052 ], 3053 'all limit and offset with sql injection of sort a custom one' => [ 3054 'coursedata' => $coursedata, 3055 'classification' => 'all', 3056 'limit' => 5, 3057 'offset' => 5, 3058 'sort' => "ul.timeaccess LIMIT 1--", 3059 'expectedcourses' => [], 3060 'expectednextoffset' => 0, 3061 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3062 ], 3063 'all limit and offset with wrong sort direction' => [ 3064 'coursedata' => $coursedata, 3065 'classification' => 'all', 3066 'limit' => 5, 3067 'offset' => 5, 3068 'sort' => "ul.timeaccess abcdasc", 3069 'expectedcourses' => [], 3070 'expectednextoffset' => 0, 3071 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()' 3072 ], 3073 'all limit and offset with wrong sort direction' => [ 3074 'coursedata' => $coursedata, 3075 'classification' => 'all', 3076 'limit' => 5, 3077 'offset' => 5, 3078 'sort' => "ul.timeaccess.foo ascd", 3079 'expectedcourses' => [], 3080 'expectednextoffset' => 0, 3081 'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()' 3082 ], 3083 'all limit and offset with wrong sort param' => [ 3084 'coursedata' => $coursedata, 3085 'classification' => 'all', 3086 'limit' => 5, 3087 'offset' => 5, 3088 'sort' => "foobar", 3089 'expectedcourses' => [], 3090 'expectednextoffset' => 0, 3091 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3092 ], 3093 'all limit and offset with wrong field name' => [ 3094 'coursedata' => $coursedata, 3095 'classification' => 'all', 3096 'limit' => 5, 3097 'offset' => 5, 3098 'sort' => "ul.foobar", 3099 'expectedcourses' => [], 3100 'expectednextoffset' => 0, 3101 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3102 ], 3103 'all limit and offset with wrong field separator' => [ 3104 'coursedata' => $coursedata, 3105 'classification' => 'all', 3106 'limit' => 5, 3107 'offset' => 5, 3108 'sort' => "ul.timeaccess.foo", 3109 'expectedcourses' => [], 3110 'expectednextoffset' => 0, 3111 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3112 ], 3113 'all limit and offset with wrong field separator #' => [ 3114 'coursedata' => $coursedata, 3115 'classification' => 'all', 3116 'limit' => 5, 3117 'offset' => 5, 3118 'sort' => "ul#timeaccess", 3119 'expectedcourses' => [], 3120 'expectednextoffset' => 0, 3121 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3122 ], 3123 'all limit and offset with wrong field separator $' => [ 3124 'coursedata' => $coursedata, 3125 'classification' => 'all', 3126 'limit' => 5, 3127 'offset' => 5, 3128 'sort' => 'ul$timeaccess', 3129 'expectedcourses' => [], 3130 'expectednextoffset' => 0, 3131 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3132 ], 3133 'all limit and offset with wrong field name' => [ 3134 'coursedata' => $coursedata, 3135 'classification' => 'all', 3136 'limit' => 5, 3137 'offset' => 5, 3138 'sort' => 'timeaccess123', 3139 'expectedcourses' => [], 3140 'expectednextoffset' => 0, 3141 'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()' 3142 ], 3143 'all limit and offset with no sort direction for ul' => [ 3144 'coursedata' => $coursedata, 3145 'classification' => 'inprogress', 3146 'limit' => 0, 3147 'offset' => 0, 3148 'sort' => "ul.timeaccess", 3149 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3150 'expectednextoffset' => 15, 3151 ], 3152 'all limit and offset with valid field name and no prefix, test for ul' => [ 3153 'coursedata' => $coursedata, 3154 'classification' => 'inprogress', 3155 'limit' => 0, 3156 'offset' => 0, 3157 'sort' => "timeaccess", 3158 'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'], 3159 'expectednextoffset' => 15, 3160 ], 3161 'all limit and offset with valid field name and no prefix' => [ 3162 'coursedata' => $coursedata, 3163 'classification' => 'all', 3164 'limit' => 5, 3165 'offset' => 5, 3166 'sort' => "fullname", 3167 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3168 'expectednextoffset' => 10, 3169 ], 3170 'all limit and offset with valid field name and no prefix and with sort direction' => [ 3171 'coursedata' => $coursedata, 3172 'classification' => 'all', 3173 'limit' => 5, 3174 'offset' => 5, 3175 'sort' => "fullname desc", 3176 'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'], 3177 'expectednextoffset' => 10, 3178 ], 3179 ]; 3180 } 3181 3182 /** 3183 * Test the get_enrolled_courses_by_timeline_classification function. 3184 * 3185 * @dataProvider get_get_enrolled_courses_by_timeline_classification_test_cases() 3186 * @param array $coursedata Courses to create 3187 * @param string $classification Timeline classification 3188 * @param int $limit Maximum number of results 3189 * @param int $offset Offset the unfiltered courses result set by this amount 3190 * @param string $sort sort the courses 3191 * @param array $expectedcourses Expected courses in result 3192 * @param int $expectednextoffset Expected next offset value in result 3193 * @param string|null $expectedexception Expected exception string 3194 */ 3195 public function test_get_enrolled_courses_by_timeline_classification( 3196 $coursedata, 3197 $classification, 3198 $limit, 3199 $offset, 3200 $sort, 3201 $expectedcourses, 3202 $expectednextoffset, 3203 $expectedexception = null 3204 ) { 3205 $this->resetAfterTest(); 3206 $generator = $this->getDataGenerator(); 3207 3208 $courses = array_map(function($coursedata) use ($generator) { 3209 return $generator->create_course($coursedata); 3210 }, $coursedata); 3211 3212 $student = $generator->create_user(); 3213 3214 foreach ($courses as $course) { 3215 $generator->enrol_user($student->id, $course->id, 'student'); 3216 } 3217 3218 $this->setUser($student); 3219 3220 if (isset($expectedexception)) { 3221 $this->expectException('coding_exception'); 3222 $this->expectExceptionMessage($expectedexception); 3223 } 3224 3225 // NOTE: The offset applies to the unfiltered full set of courses before the classification 3226 // filtering is done. 3227 // E.g. In our example if an offset of 2 is given then it would mean the first 3228 // two courses (afuture, ainprogress) are ignored. 3229 $result = core_course_external::get_enrolled_courses_by_timeline_classification( 3230 $classification, 3231 $limit, 3232 $offset, 3233 $sort 3234 ); 3235 $result = external_api::clean_returnvalue( 3236 core_course_external::get_enrolled_courses_by_timeline_classification_returns(), 3237 $result 3238 ); 3239 3240 $actual = array_map(function($course) { 3241 return $course['shortname']; 3242 }, $result['courses']); 3243 3244 $this->assertEqualsCanonicalizing($expectedcourses, $actual); 3245 $this->assertEquals($expectednextoffset, $result['nextoffset']); 3246 } 3247 3248 /** 3249 * Test the get_recent_courses function. 3250 */ 3251 public function test_get_recent_courses() { 3252 global $USER, $DB; 3253 3254 $this->resetAfterTest(); 3255 $generator = $this->getDataGenerator(); 3256 3257 set_config('hiddenuserfields', 'lastaccess'); 3258 3259 $courses = array(); 3260 for ($i = 1; $i < 12; $i++) { 3261 $courses[] = $generator->create_course(); 3262 }; 3263 3264 $student = $generator->create_user(); 3265 $teacher = $generator->create_user(); 3266 3267 foreach ($courses as $course) { 3268 $generator->enrol_user($student->id, $course->id, 'student'); 3269 } 3270 3271 $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher'); 3272 3273 $this->setUser($student); 3274 3275 $result = core_course_external::get_recent_courses($USER->id); 3276 3277 // No course accessed. 3278 $this->assertCount(0, $result); 3279 3280 foreach ($courses as $course) { 3281 core_course_external::view_course($course->id); 3282 } 3283 3284 // Every course accessed. 3285 $result = core_course_external::get_recent_courses($USER->id); 3286 $this->assertCount( 11, $result); 3287 3288 // Every course accessed, result limited to 10 courses. 3289 $result = core_course_external::get_recent_courses($USER->id, 10); 3290 $this->assertCount(10, $result); 3291 3292 $guestcourse = $generator->create_course( 3293 (object)array('shortname' => 'guestcourse', 3294 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, 3295 'enrol_guest_password_0' => '')); 3296 core_course_external::view_course($guestcourse->id); 3297 3298 // Every course accessed, even the not enrolled one. 3299 $result = core_course_external::get_recent_courses($USER->id); 3300 $this->assertCount(12, $result); 3301 3302 // Offset 5, return 7 out of 12. 3303 $result = core_course_external::get_recent_courses($USER->id, 0, 5); 3304 $this->assertCount(7, $result); 3305 3306 // Offset 5 and limit 3, return 3 out of 12. 3307 $result = core_course_external::get_recent_courses($USER->id, 3, 5); 3308 $this->assertCount(3, $result); 3309 3310 // Sorted by course id ASC. 3311 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id ASC'); 3312 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3313 3314 // Sorted by course id DESC. 3315 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id DESC'); 3316 $this->assertEquals($guestcourse->id, array_shift($result)->id); 3317 3318 // If last access is hidden, only get the courses where has viewhiddenuserfields capability. 3319 $this->setUser($teacher); 3320 $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 3321 $usercontext = context_user::instance($student->id); 3322 $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid); 3323 3324 // Sorted by course id DESC. 3325 $result = core_course_external::get_recent_courses($student->id); 3326 $this->assertCount(1, $result); 3327 $this->assertEquals($courses[0]->id, array_shift($result)->id); 3328 } 3329 3330 /** 3331 * Test get enrolled users by cmid function. 3332 */ 3333 public function test_get_enrolled_users_by_cmid() { 3334 global $PAGE; 3335 $this->resetAfterTest(true); 3336 3337 $user1 = self::getDataGenerator()->create_user(); 3338 $user2 = self::getDataGenerator()->create_user(); 3339 3340 $user1picture = new user_picture($user1); 3341 $user1picture->size = 1; 3342 $user1->profileimage = $user1picture->get_url($PAGE)->out(false); 3343 3344 $user2picture = new user_picture($user2); 3345 $user2picture->size = 1; 3346 $user2->profileimage = $user2picture->get_url($PAGE)->out(false); 3347 3348 // Set the first created user to the test user. 3349 self::setUser($user1); 3350 3351 // Create course to add the module. 3352 $course1 = self::getDataGenerator()->create_course(); 3353 3354 // Forum with tracking off. 3355 $record = new stdClass(); 3356 $record->course = $course1->id; 3357 $forum1 = self::getDataGenerator()->create_module('forum', $record); 3358 3359 // Following lines enrol and assign default role id to the users. 3360 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 3361 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 3362 3363 // Create what we expect to be returned when querying the course module. 3364 $expectedusers = array( 3365 'users' => array(), 3366 'warnings' => array(), 3367 ); 3368 3369 $expectedusers['users'][0] = [ 3370 'id' => $user1->id, 3371 'fullname' => fullname($user1), 3372 'firstname' => $user1->firstname, 3373 'lastname' => $user1->lastname, 3374 'profileimage' => $user1->profileimage, 3375 ]; 3376 $expectedusers['users'][1] = [ 3377 'id' => $user2->id, 3378 'fullname' => fullname($user2), 3379 'firstname' => $user2->firstname, 3380 'lastname' => $user2->lastname, 3381 'profileimage' => $user2->profileimage, 3382 ]; 3383 3384 // Test getting the users in a given context. 3385 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid); 3386 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users); 3387 3388 $this->assertEquals(2, count($users['users'])); 3389 $this->assertEquals($expectedusers, $users); 3390 } 3391 3392 /** 3393 * Verify that content items can be added to user favourites. 3394 */ 3395 public function test_add_content_item_to_user_favourites() { 3396 $this->resetAfterTest(); 3397 3398 $course = $this->getDataGenerator()->create_course(); 3399 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3400 $this->setUser($user); 3401 3402 // Using the internal API, confirm that no items are set as favourites for the user. 3403 $contentitemservice = new \core_course\local\service\content_item_service( 3404 new \core_course\local\repository\content_item_readonly_repository() 3405 ); 3406 $contentitems = $contentitemservice->get_all_content_items($user); 3407 $favourited = array_filter($contentitems, function($contentitem) { 3408 return $contentitem->favourite == true; 3409 }); 3410 $this->assertCount(0, $favourited); 3411 3412 // Using the external API, favourite a content item for the user. 3413 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3414 $contentitem = core_course_external::add_content_item_to_user_favourites('mod_assign', $assign->id, $user->id); 3415 $contentitem = external_api::clean_returnvalue(core_course_external::add_content_item_to_user_favourites_returns(), 3416 $contentitem); 3417 3418 // Verify the returned item is a favourite. 3419 $this->assertTrue($contentitem['favourite']); 3420 3421 // Using the internal API, confirm we see a single favourite item. 3422 $contentitems = $contentitemservice->get_all_content_items($user); 3423 $favourited = array_values(array_filter($contentitems, function($contentitem) { 3424 return $contentitem->favourite == true; 3425 })); 3426 $this->assertCount(1, $favourited); 3427 $this->assertEquals('assign', $favourited[0]->name); 3428 } 3429 3430 /** 3431 * Verify that content items can be removed from user favourites. 3432 */ 3433 public function test_remove_content_item_from_user_favourites() { 3434 $this->resetAfterTest(); 3435 3436 $course = $this->getDataGenerator()->create_course(); 3437 $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3438 $this->setUser($user); 3439 3440 // Using the internal API, set a favourite for the user. 3441 $contentitemservice = new \core_course\local\service\content_item_service( 3442 new \core_course\local\repository\content_item_readonly_repository() 3443 ); 3444 $contentitems = $contentitemservice->get_all_content_items($user); 3445 $assign = $contentitems[array_search('assign', array_column($contentitems, 'name'))]; 3446 $contentitemservice->add_to_user_favourites($user, $assign->componentname, $assign->id); 3447 3448 $contentitems = $contentitemservice->get_all_content_items($user); 3449 $favourited = array_filter($contentitems, function($contentitem) { 3450 return $contentitem->favourite == true; 3451 }); 3452 $this->assertCount(1, $favourited); 3453 3454 // Now, verify the external API can remove the favourite. 3455 $contentitem = core_course_external::remove_content_item_from_user_favourites('mod_assign', $assign->id); 3456 $contentitem = external_api::clean_returnvalue(core_course_external::remove_content_item_from_user_favourites_returns(), 3457 $contentitem); 3458 3459 // Verify the returned item is a favourite. 3460 $this->assertFalse($contentitem['favourite']); 3461 3462 // Using the internal API, confirm we see no favourite items. 3463 $contentitems = $contentitemservice->get_all_content_items($user); 3464 $favourited = array_filter($contentitems, function($contentitem) { 3465 return $contentitem->favourite == true; 3466 }); 3467 $this->assertCount(0, $favourited); 3468 } 3469 3470 /** 3471 * Test the web service returning course content items for inclusion in activity choosers, etc. 3472 */ 3473 public function test_get_course_content_items() { 3474 $this->resetAfterTest(); 3475 3476 $course = self::getDataGenerator()->create_course(); 3477 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 3478 3479 // Fetch available content items as the editing teacher. 3480 $this->setUser($user); 3481 $result = core_course_external::get_course_content_items($course->id); 3482 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3483 3484 $contentitemservice = new \core_course\local\service\content_item_service( 3485 new \core_course\local\repository\content_item_readonly_repository() 3486 ); 3487 3488 // Check if the webservice returns exactly what the service defines, albeit in array form. 3489 $serviceitemsasarray = array_map(function($item) { 3490 return (array) $item; 3491 }, $contentitemservice->get_content_items_for_user_in_course($user, $course)); 3492 3493 $this->assertEquals($serviceitemsasarray, $result['content_items']); 3494 } 3495 3496 /** 3497 * Test the web service returning course content items, specifically in case where the user can't manage activities. 3498 */ 3499 public function test_get_course_content_items_no_permission_to_manage() { 3500 $this->resetAfterTest(); 3501 3502 $course = self::getDataGenerator()->create_course(); 3503 $user = self::getDataGenerator()->create_and_enrol($course, 'student'); 3504 3505 // Fetch available content items as a student, who won't have the permission to manage activities. 3506 $this->setUser($user); 3507 $result = core_course_external::get_course_content_items($course->id); 3508 $result = external_api::clean_returnvalue(core_course_external::get_course_content_items_returns(), $result); 3509 3510 $this->assertEmpty($result['content_items']); 3511 } 3512 3513 /** 3514 * Test toggling the recommendation of an activity. 3515 */ 3516 public function test_toggle_activity_recommendation() { 3517 global $CFG; 3518 3519 $this->resetAfterTest(); 3520 3521 $context = context_system::instance(); 3522 $usercontext = context_user::instance($CFG->siteguest); 3523 $component = 'core_course'; 3524 $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext); 3525 3526 $areaname = 'test_core'; 3527 $areaid = 3; 3528 3529 // Test we have the favourite. 3530 $this->setAdminUser(); 3531 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3532 $this->assertTrue($favouritefactory->favourite_exists($component, 3533 \core_course\local\service\content_item_service::RECOMMENDATION_PREFIX . $areaname, $areaid, $context)); 3534 $this->assertTrue($result['status']); 3535 // Test that it is now gone. 3536 $result = core_course_external::toggle_activity_recommendation($areaname, $areaid); 3537 $this->assertFalse($favouritefactory->favourite_exists($component, $areaname, $areaid, $context)); 3538 $this->assertFalse($result['status']); 3539 } 3540 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body