2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * External course functions unit tests
20 * @package core_course
22 * @copyright 2012 Jerome Mouneyrac
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') ||
die();
30 require_once($CFG->dirroot
. '/webservice/tests/helpers.php');
33 * External course functions unit tests
35 * @package core_course
37 * @copyright 2012 Jerome Mouneyrac
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class core_course_externallib_testcase
extends externallib_advanced_testcase
{
45 protected function setUp() {
47 require_once($CFG->dirroot
. '/course/externallib.php');
51 * Test create_categories
53 public function test_create_categories() {
57 $this->resetAfterTest(true);
59 // Set the required capabilities by the external function
60 $contextid = context_system
::instance()->id
;
61 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
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
= 'bootstrapbase';
72 array('name' => $category1->name
, 'parent' => 0),
73 array('name' => $category2->name
, 'parent' => 0, 'idnumber' => $category2->idnumber
,
74 'description' => $category2->desc
, 'theme' => $category2->theme
)
77 $createdcats = core_course_external
::create_categories($categories);
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);
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']);
87 $category1->id
= $createdcats[0]['id'];
88 $category2->id
= $createdcats[1]['id'];
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
)
97 $createdsubcats = core_course_external
::create_categories($subcategories);
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);
102 // Confirm that sub categories were inserted correctly.
103 $this->assertEquals($category3->name
, $createdsubcats[0]['name']);
106 $category3->id
= $createdsubcats[0]['id'];
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
));
114 // sortorder sequence (and sortorder) must be:
118 $this->assertGreaterThan($category1->sortorder
, $category3->sortorder
);
119 $this->assertGreaterThan($category3->sortorder
, $category2->sortorder
);
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);
129 * Test delete categories
131 public function test_delete_categories() {
134 $this->resetAfterTest(true);
136 // Set the required capabilities by the external function
137 $contextid = context_system
::instance()->id
;
138 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
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
));
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
)
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);
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
);
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
)));
172 * Test get categories
174 public function test_get_categories() {
177 $this->resetAfterTest(true);
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;
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);
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);
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);
214 // Check we retrieve the good total number of categories.
215 $this->assertEquals(2, count($categories));
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
);
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);
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);
235 // Check we retrieve the good total number of categories.
236 $this->assertEquals(6, count($categories));
239 foreach ($categories as $category) {
240 $returnedids[] = $category['id'];
242 // Sort the arrays upon comparision.
243 $this->assertEquals(array_keys($generatedcats), $returnedids, '', 0.0, 10, true);
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);
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);
255 $this->assertEquals(1, count($categories));
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);
265 $this->assertEquals(1, count($categories));
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);
272 $this->assertEquals(2, count($categories));
274 // Retrieve all categories.
275 $categories = core_course_external
::get_categories();
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);
280 $this->assertEquals($DB->count_records('course_categories'), count($categories));
282 $this->unassignUserCapability('moodle/category:viewhiddencategories', $context->id
, $roleid);
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();
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);
292 $this->assertEquals($DB->count_records('course_categories', array('visible' => 1)), count($categories));
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);
303 * Test update_categories
305 public function test_update_categories() {
308 $this->resetAfterTest(true);
310 // Set the required capabilities by the external function
311 $contextid = context_system
::instance()->id
;
312 $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
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
));
328 // We update all category1 attribut.
329 // Then we move cat4 and cat5 parent: cat3 => cat1
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
));
339 core_course_external
::update_categories($categories);
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
);
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
);
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);
366 * Test create_courses numsections
368 public function test_create_course_numsections() {
371 $this->resetAfterTest(true);
373 // Set the required capabilities by the external function.
374 $contextid = context_system
::instance()->id
;
375 $roleid = $this->assignUserCapability('moodle/course:create', $contextid);
376 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
379 $category = self
::getDataGenerator()->create_category();
381 // Create base categories.
382 $course1['fullname'] = 'Test course 1';
383 $course1['shortname'] = 'Testcourse1';
384 $course1['categoryid'] = $category->id
;
385 $course1['courseformatoptions'][] = array('name' => 'numsections', 'value' => $numsections);
387 $courses = array($course1);
389 $createdcourses = core_course_external
::create_courses($courses);
390 foreach ($createdcourses as $createdcourse) {
391 $existingsections = $DB->get_records('course_sections', array('course' => $createdcourse['id']));
392 $modinfo = get_fast_modinfo($createdcourse['id']);
393 $sections = $modinfo->get_section_info_all();
394 $this->assertEquals(count($sections), $numsections +
1); // Includes generic section.
395 $this->assertEquals(count($existingsections), $numsections +
1); // Includes generic section.
400 * Test create_courses
402 public function test_create_courses() {
405 $this->resetAfterTest(true);
407 // Enable course completion.
408 set_config('enablecompletion', 1);
409 // Enable course themes.
410 set_config('allowcoursethemes', 1);
412 // Set the required capabilities by the external function
413 $contextid = context_system
::instance()->id
;
414 $roleid = $this->assignUserCapability('moodle/course:create', $contextid);
415 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
416 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid);
418 $category = self
::getDataGenerator()->create_category();
420 // Create base categories.
421 $course1['fullname'] = 'Test course 1';
422 $course1['shortname'] = 'Testcourse1';
423 $course1['categoryid'] = $category->id
;
424 $course2['fullname'] = 'Test course 2';
425 $course2['shortname'] = 'Testcourse2';
426 $course2['categoryid'] = $category->id
;
427 $course2['idnumber'] = 'testcourse2idnumber';
428 $course2['summary'] = 'Description for course 2';
429 $course2['summaryformat'] = FORMAT_MOODLE
;
430 $course2['format'] = 'weeks';
431 $course2['showgrades'] = 1;
432 $course2['newsitems'] = 3;
433 $course2['startdate'] = 1420092000; // 01/01/2015.
434 $course2['enddate'] = 1422669600; // 01/31/2015.
435 $course2['numsections'] = 4;
436 $course2['maxbytes'] = 100000;
437 $course2['showreports'] = 1;
438 $course2['visible'] = 0;
439 $course2['hiddensections'] = 0;
440 $course2['groupmode'] = 0;
441 $course2['groupmodeforce'] = 0;
442 $course2['defaultgroupingid'] = 0;
443 $course2['enablecompletion'] = 1;
444 $course2['completionnotify'] = 1;
445 $course2['lang'] = 'en';
446 $course2['forcetheme'] = 'bootstrapbase';
447 $course2['courseformatoptions'][] = array('name' => 'automaticenddate', 'value' => 0);
448 $course3['fullname'] = 'Test course 3';
449 $course3['shortname'] = 'Testcourse3';
450 $course3['categoryid'] = $category->id
;
451 $course3['format'] = 'topics';
452 $course3options = array('numsections' => 8,
453 'hiddensections' => 1,
454 'coursedisplay' => 1);
455 $course3['courseformatoptions'] = array();
456 foreach ($course3options as $key => $value) {
457 $course3['courseformatoptions'][] = array('name' => $key, 'value' => $value);
459 $courses = array($course1, $course2, $course3);
461 $createdcourses = core_course_external
::create_courses($courses);
463 // We need to execute the return values cleaning process to simulate the web service server.
464 $createdcourses = external_api
::clean_returnvalue(core_course_external
::create_courses_returns(), $createdcourses);
466 // Check that right number of courses were created.
467 $this->assertEquals(3, count($createdcourses));
469 // Check that the courses were correctly created.
470 foreach ($createdcourses as $createdcourse) {
471 $courseinfo = course_get_format($createdcourse['id'])->get_course();
473 if ($createdcourse['shortname'] == $course2['shortname']) {
474 $this->assertEquals($courseinfo->fullname
, $course2['fullname']);
475 $this->assertEquals($courseinfo->shortname
, $course2['shortname']);
476 $this->assertEquals($courseinfo->category
, $course2['categoryid']);
477 $this->assertEquals($courseinfo->idnumber
, $course2['idnumber']);
478 $this->assertEquals($courseinfo->summary
, $course2['summary']);
479 $this->assertEquals($courseinfo->summaryformat
, $course2['summaryformat']);
480 $this->assertEquals($courseinfo->format
, $course2['format']);
481 $this->assertEquals($courseinfo->showgrades
, $course2['showgrades']);
482 $this->assertEquals($courseinfo->newsitems
, $course2['newsitems']);
483 $this->assertEquals($courseinfo->startdate
, $course2['startdate']);
484 $this->assertEquals($courseinfo->enddate
, $course2['enddate']);
485 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(), $course2['numsections']);
486 $this->assertEquals($courseinfo->maxbytes
, $course2['maxbytes']);
487 $this->assertEquals($courseinfo->showreports
, $course2['showreports']);
488 $this->assertEquals($courseinfo->visible
, $course2['visible']);
489 $this->assertEquals($courseinfo->hiddensections
, $course2['hiddensections']);
490 $this->assertEquals($courseinfo->groupmode
, $course2['groupmode']);
491 $this->assertEquals($courseinfo->groupmodeforce
, $course2['groupmodeforce']);
492 $this->assertEquals($courseinfo->defaultgroupingid
, $course2['defaultgroupingid']);
493 $this->assertEquals($courseinfo->completionnotify
, $course2['completionnotify']);
494 $this->assertEquals($courseinfo->lang
, $course2['lang']);
495 $this->assertEquals($courseinfo->theme
, $course2['forcetheme']);
497 // We enabled completion at the beginning of the test.
498 $this->assertEquals($courseinfo->enablecompletion
, $course2['enablecompletion']);
500 } else if ($createdcourse['shortname'] == $course1['shortname']) {
501 $courseconfig = get_config('moodlecourse');
502 $this->assertEquals($courseinfo->fullname
, $course1['fullname']);
503 $this->assertEquals($courseinfo->shortname
, $course1['shortname']);
504 $this->assertEquals($courseinfo->category
, $course1['categoryid']);
505 $this->assertEquals($courseinfo->summaryformat
, FORMAT_HTML
);
506 $this->assertEquals($courseinfo->format
, $courseconfig->format
);
507 $this->assertEquals($courseinfo->showgrades
, $courseconfig->showgrades
);
508 $this->assertEquals($courseinfo->newsitems
, $courseconfig->newsitems
);
509 $this->assertEquals($courseinfo->maxbytes
, $courseconfig->maxbytes
);
510 $this->assertEquals($courseinfo->showreports
, $courseconfig->showreports
);
511 $this->assertEquals($courseinfo->groupmode
, $courseconfig->groupmode
);
512 $this->assertEquals($courseinfo->groupmodeforce
, $courseconfig->groupmodeforce
);
513 $this->assertEquals($courseinfo->defaultgroupingid
, 0);
514 } else if ($createdcourse['shortname'] == $course3['shortname']) {
515 $this->assertEquals($courseinfo->fullname
, $course3['fullname']);
516 $this->assertEquals($courseinfo->shortname
, $course3['shortname']);
517 $this->assertEquals($courseinfo->category
, $course3['categoryid']);
518 $this->assertEquals($courseinfo->format
, $course3['format']);
519 $this->assertEquals($courseinfo->hiddensections
, $course3options['hiddensections']);
520 $this->assertEquals(course_get_format($createdcourse['id'])->get_last_section_number(),
521 $course3options['numsections']);
522 $this->assertEquals($courseinfo->coursedisplay
, $course3options['coursedisplay']);
524 throw new moodle_exception('Unexpected shortname');
528 // Call without required capability
529 $this->unassignUserCapability('moodle/course:create', $contextid, $roleid);
530 $this->expectException('required_capability_exception');
531 $createdsubcats = core_course_external
::create_courses($courses);
535 * Test delete_courses
537 public function test_delete_courses() {
540 $this->resetAfterTest(true);
542 // Admin can delete a course.
543 $this->setAdminUser();
544 // Validate_context() will fail as the email is not set by $this->setAdminUser().
545 $USER->email
= 'emailtopass@example.com';
547 $course1 = self
::getDataGenerator()->create_course();
548 $course2 = self
::getDataGenerator()->create_course();
549 $course3 = self
::getDataGenerator()->create_course();
552 $result = core_course_external
::delete_courses(array($course1->id
, $course2->id
));
553 $result = external_api
::clean_returnvalue(core_course_external
::delete_courses_returns(), $result);
554 // Check for 0 warnings.
555 $this->assertEquals(0, count($result['warnings']));
557 // Check $course 1 and 2 are deleted.
558 $notdeletedcount = $DB->count_records_select('course',
559 'id IN ( ' . $course1->id
. ',' . $course2->id
. ')');
560 $this->assertEquals(0, $notdeletedcount);
562 // Try to delete non-existent course.
563 $result = core_course_external
::delete_courses(array($course1->id
));
564 $result = external_api
::clean_returnvalue(core_course_external
::delete_courses_returns(), $result);
565 // Check for 1 warnings.
566 $this->assertEquals(1, count($result['warnings']));
568 // Try to delete Frontpage course.
569 $result = core_course_external
::delete_courses(array(0));
570 $result = external_api
::clean_returnvalue(core_course_external
::delete_courses_returns(), $result);
571 // Check for 1 warnings.
572 $this->assertEquals(1, count($result['warnings']));
574 // Fail when the user has access to course (enrolled) but does not have permission or is not admin.
575 $student1 = self
::getDataGenerator()->create_user();
576 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
577 $this->getDataGenerator()->enrol_user($student1->id
,
580 $this->setUser($student1);
581 $result = core_course_external
::delete_courses(array($course3->id
));
582 $result = external_api
::clean_returnvalue(core_course_external
::delete_courses_returns(), $result);
583 // Check for 1 warnings.
584 $this->assertEquals(1, count($result['warnings']));
586 // Fail when the user is not allow to access the course (enrolled) or is not admin.
587 $this->setGuestUser();
588 $this->expectException('require_login_exception');
590 $result = core_course_external
::delete_courses(array($course3->id
));
591 $result = external_api
::clean_returnvalue(core_course_external
::delete_courses_returns(), $result);
597 public function test_get_courses () {
600 $this->resetAfterTest(true);
602 $generatedcourses = array();
603 $coursedata['idnumber'] = 'idnumbercourse1';
604 // Adding tags here to check that format_string is applied.
605 $coursedata['fullname'] = '<b>Course 1 for PHPunit test</b>';
606 $coursedata['shortname'] = '<b>Course 1 for PHPunit test</b>';
607 $coursedata['summary'] = 'Course 1 description';
608 $coursedata['summaryformat'] = FORMAT_MOODLE
;
609 $course1 = self
::getDataGenerator()->create_course($coursedata);
611 $generatedcourses[$course1->id
] = $course1;
612 $course2 = self
::getDataGenerator()->create_course();
613 $generatedcourses[$course2->id
] = $course2;
614 $course3 = self
::getDataGenerator()->create_course(array('format' => 'topics'));
615 $generatedcourses[$course3->id
] = $course3;
617 // Set the required capabilities by the external function.
618 $context = context_system
::instance();
619 $roleid = $this->assignUserCapability('moodle/course:view', $context->id
);
620 $this->assignUserCapability('moodle/course:update',
621 context_course
::instance($course1->id
)->id
, $roleid);
622 $this->assignUserCapability('moodle/course:update',
623 context_course
::instance($course2->id
)->id
, $roleid);
624 $this->assignUserCapability('moodle/course:update',
625 context_course
::instance($course3->id
)->id
, $roleid);
627 $courses = core_course_external
::get_courses(array('ids' =>
628 array($course1->id
, $course2->id
)));
630 // We need to execute the return values cleaning process to simulate the web service server.
631 $courses = external_api
::clean_returnvalue(core_course_external
::get_courses_returns(), $courses);
633 // Check we retrieve the good total number of categories.
634 $this->assertEquals(2, count($courses));
636 foreach ($courses as $course) {
637 $coursecontext = context_course
::instance($course['id']);
638 $dbcourse = $generatedcourses[$course['id']];
639 $this->assertEquals($course['idnumber'], $dbcourse->idnumber
);
640 $this->assertEquals($course['fullname'], external_format_string($dbcourse->fullname
, $coursecontext->id
));
641 $this->assertEquals($course['displayname'], external_format_string(get_course_display_name_for_list($dbcourse),
642 $coursecontext->id
));
643 // Summary was converted to the HTML format.
644 $this->assertEquals($course['summary'], format_text($dbcourse->summary
, FORMAT_MOODLE
, array('para' => false)));
645 $this->assertEquals($course['summaryformat'], FORMAT_HTML
);
646 $this->assertEquals($course['shortname'], external_format_string($dbcourse->shortname
, $coursecontext->id
));
647 $this->assertEquals($course['categoryid'], $dbcourse->category
);
648 $this->assertEquals($course['format'], $dbcourse->format
);
649 $this->assertEquals($course['showgrades'], $dbcourse->showgrades
);
650 $this->assertEquals($course['newsitems'], $dbcourse->newsitems
);
651 $this->assertEquals($course['startdate'], $dbcourse->startdate
);
652 $this->assertEquals($course['enddate'], $dbcourse->enddate
);
653 $this->assertEquals($course['numsections'], course_get_format($dbcourse)->get_last_section_number());
654 $this->assertEquals($course['maxbytes'], $dbcourse->maxbytes
);
655 $this->assertEquals($course['showreports'], $dbcourse->showreports
);
656 $this->assertEquals($course['visible'], $dbcourse->visible
);
657 $this->assertEquals($course['hiddensections'], $dbcourse->hiddensections
);
658 $this->assertEquals($course['groupmode'], $dbcourse->groupmode
);
659 $this->assertEquals($course['groupmodeforce'], $dbcourse->groupmodeforce
);
660 $this->assertEquals($course['defaultgroupingid'], $dbcourse->defaultgroupingid
);
661 $this->assertEquals($course['completionnotify'], $dbcourse->completionnotify
);
662 $this->assertEquals($course['lang'], $dbcourse->lang
);
663 $this->assertEquals($course['forcetheme'], $dbcourse->theme
);
664 $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion
);
665 if ($dbcourse->format
=== 'topics') {
666 $this->assertEquals($course['courseformatoptions'], array(
667 array('name' => 'hiddensections', 'value' => $dbcourse->hiddensections
),
668 array('name' => 'coursedisplay', 'value' => $dbcourse->coursedisplay
),
673 // Get all courses in the DB
674 $courses = core_course_external
::get_courses(array());
676 // We need to execute the return values cleaning process to simulate the web service server.
677 $courses = external_api
::clean_returnvalue(core_course_external
::get_courses_returns(), $courses);
679 $this->assertEquals($DB->count_records('course'), count($courses));
683 * Test get_courses without capability
685 public function test_get_courses_without_capability() {
686 $this->resetAfterTest(true);
688 $course1 = $this->getDataGenerator()->create_course();
689 $this->setUser($this->getDataGenerator()->create_user());
691 // No permissions are required to get the site course.
692 $courses = core_course_external
::get_courses(array('ids' => [SITEID
]));
693 $courses = external_api
::clean_returnvalue(core_course_external
::get_courses_returns(), $courses);
695 $this->assertEquals(1, count($courses));
696 $this->assertEquals('PHPUnit test site', $courses[0]['fullname']);
697 $this->assertEquals('site', $courses[0]['format']);
699 // Requesting course without being enrolled or capability to view it will throw an exception.
701 core_course_external
::get_courses(array('ids' => [$course1->id
]));
702 $this->fail('Exception expected');
703 } catch (moodle_exception
$e) {
704 $this->assertEquals(1, preg_match('/Course or activity not accessible. \(Not enrolled\)/', $e->getMessage()));
709 * Test search_courses
711 public function test_search_courses () {
715 $this->resetAfterTest(true);
716 $this->setAdminUser();
717 $generatedcourses = array();
718 $coursedata1['fullname'] = 'FIRST COURSE';
719 $course1 = self
::getDataGenerator()->create_course($coursedata1);
721 $page = new moodle_page();
722 $page->set_course($course1);
723 $page->blocks
->add_blocks([BLOCK_POS_LEFT
=> ['news_items'], BLOCK_POS_RIGHT
=> []], 'course-view-*');
725 $coursedata2['fullname'] = 'SECOND COURSE';
726 $course2 = self
::getDataGenerator()->create_course($coursedata2);
728 $page = new moodle_page();
729 $page->set_course($course2);
730 $page->blocks
->add_blocks([BLOCK_POS_LEFT
=> ['news_items'], BLOCK_POS_RIGHT
=> []], 'course-view-*');
732 $results = core_course_external
::search_courses('search', 'FIRST');
733 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
734 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']);
735 $this->assertCount(1, $results['courses']);
738 $record = new stdClass();
739 $record->introformat
= FORMAT_HTML
;
740 $record->course
= $course2->id
;
741 // Set Aggregate type = Average of ratings.
742 $forum = self
::getDataGenerator()->create_module('forum', $record);
745 $results = core_course_external
::search_courses('modulelist', 'forum');
746 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
747 $this->assertEquals(1, $results['total']);
749 // Enable coursetag option.
750 set_config('block_tags_showcoursetags', true);
751 // Add tag 'TAG-LABEL ON SECOND COURSE' to Course2.
752 core_tag_tag
::set_item_tags('core', 'course', $course2->id
, context_course
::instance($course2->id
),
753 array('TAG-LABEL ON SECOND COURSE'));
754 $taginstance = $DB->get_record('tag_instance',
755 array('itemtype' => 'course', 'itemid' => $course2->id
), '*', MUST_EXIST
);
757 $results = core_course_external
::search_courses('tagid', $taginstance->tagid
);
758 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
759 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']);
761 // Search by block (use news_items default block).
762 $blockid = $DB->get_field('block', 'id', array('name' => 'news_items'));
763 $results = core_course_external
::search_courses('blocklist', $blockid);
764 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
765 $this->assertEquals(2, $results['total']);
767 // Now as a normal user.
768 $user = self
::getDataGenerator()->create_user();
770 // Add a 3rd, hidden, course we shouldn't see, even when enrolled as student.
771 $coursedata3['fullname'] = 'HIDDEN COURSE';
772 $coursedata3['visible'] = 0;
773 $course3 = self
::getDataGenerator()->create_course($coursedata3);
774 $this->getDataGenerator()->enrol_user($user->id
, $course3->id
, 'student');
776 $this->getDataGenerator()->enrol_user($user->id
, $course2->id
, 'student');
777 $this->setUser($user);
779 $results = core_course_external
::search_courses('search', 'FIRST');
780 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
781 $this->assertCount(1, $results['courses']);
782 $this->assertEquals(1, $results['total']);
783 $this->assertEquals($coursedata1['fullname'], $results['courses'][0]['fullname']);
785 // Check that we can see both without the limit to enrolled setting.
786 $results = core_course_external
::search_courses('search', 'COURSE', 0, 0, array(), 0);
787 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
788 $this->assertCount(2, $results['courses']);
789 $this->assertEquals(2, $results['total']);
791 // Check that we only see our enrolled course when limiting.
792 $results = core_course_external
::search_courses('search', 'COURSE', 0, 0, array(), 1);
793 $results = external_api
::clean_returnvalue(core_course_external
::search_courses_returns(), $results);
794 $this->assertCount(1, $results['courses']);
795 $this->assertEquals(1, $results['total']);
796 $this->assertEquals($coursedata2['fullname'], $results['courses'][0]['fullname']);
798 // Search by block (use news_items default block). Should fail (only admins allowed).
799 $this->expectException('required_capability_exception');
800 $results = core_course_external
::search_courses('blocklist', $blockid);
805 * Create a course with contents
806 * @return array A list with the course object and course modules objects
808 private function prepare_get_course_contents_test() {
811 $CFG->allowstealth
= 1; // Allow stealth activities.
812 $CFG->enablecompletion
= true;
813 $course = self
::getDataGenerator()->create_course(['numsections' => 4, 'enablecompletion' => 1]);
815 $forumdescription = 'This is the forum description';
816 $forum = $this->getDataGenerator()->create_module('forum',
817 array('course' => $course->id
, 'intro' => $forumdescription, 'trackingtype' => 2),
818 array('showdescription' => true, 'completion' => COMPLETION_TRACKING_MANUAL
));
819 $forumcm = get_coursemodule_from_id('forum', $forum->cmid
);
820 // Add discussions to the tracking forced forum.
821 $record = new stdClass();
822 $record->course
= $course->id
;
824 $record->forum
= $forum->id
;
825 $discussionforce = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
826 $data = $this->getDataGenerator()->create_module('data',
827 array('assessed' => 1, 'scale' => 100, 'course' => $course->id
, 'completion' => 2, 'completionentries' => 3));
828 $datacm = get_coursemodule_from_instance('data', $data->id
);
829 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id
));
830 $pagecm = get_coursemodule_from_instance('page', $page->id
);
831 // This is an stealth page (set by visibleoncoursepage).
832 $pagestealth = $this->getDataGenerator()->create_module('page', array('course' => $course->id
, 'visibleoncoursepage' => 0));
833 $labeldescription = 'This is a very long label to test if more than 50 characters are returned.
834 So bla bla bla bla <b>bold bold bold</b> bla bla bla bla.';
835 $label = $this->getDataGenerator()->create_module('label', array('course' => $course->id
,
836 'intro' => $labeldescription));
837 $labelcm = get_coursemodule_from_instance('label', $label->id
);
838 $tomorrow = time() + DAYSECS
;
839 // Module with availability restrictions not met.
840 $url = $this->getDataGenerator()->create_module('url',
841 array('course' => $course->id
, 'name' => 'URL: % & $ ../', 'section' => 2, 'display' => RESOURCELIB_DISPLAY_POPUP
,
842 'popupwidth' => 100, 'popupheight' => 100),
843 array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '}],"showc":[true]}'));
844 $urlcm = get_coursemodule_from_instance('url', $url->id
);
845 // Module for the last section.
846 $this->getDataGenerator()->create_module('url',
847 array('course' => $course->id
, 'name' => 'URL for last section', 'section' => 3));
848 // Module for section 1 with availability restrictions met.
849 $yesterday = time() - DAYSECS
;
850 $this->getDataGenerator()->create_module('url',
851 array('course' => $course->id
, 'name' => 'URL restrictions met', 'section' => 1),
852 array('availability' => '{"op":"&","c":[{"type":"date","d":">=","t":'. $yesterday .'}],"showc":[true]}'));
854 // Set the required capabilities by the external function.
855 $context = context_course
::instance($course->id
);
856 $roleid = $this->assignUserCapability('moodle/course:view', $context->id
);
857 $this->assignUserCapability('moodle/course:update', $context->id
, $roleid);
858 $this->assignUserCapability('mod/data:view', $context->id
, $roleid);
860 $conditions = array('course' => $course->id
, 'section' => 2);
861 $DB->set_field('course_sections', 'summary', 'Text with iframe <iframe src="https://moodle.org"></iframe>', $conditions);
863 // Add date availability condition not met for section 3.
864 $availability = '{"op":"&","c":[{"type":"date","d":">=","t":' . $tomorrow . '}],"showc":[true]}';
865 $DB->set_field('course_sections', 'availability', $availability,
866 array('course' => $course->id
, 'section' => 3));
868 // Create resource for last section.
869 $pageinhiddensection = $this->getDataGenerator()->create_module('page',
870 array('course' => $course->id
, 'name' => 'Page in hidden section', 'section' => 4));
871 // Set not visible last section.
872 $DB->set_field('course_sections', 'visible', 0,
873 array('course' => $course->id
, 'section' => 4));
875 rebuild_course_cache($course->id
, true);
877 return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm);
881 * Test get_course_contents
883 public function test_get_course_contents() {
885 $this->resetAfterTest(true);
887 $CFG->forum_allowforcedreadtracking
= 1;
888 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
890 // We first run the test as admin.
891 $this->setAdminUser();
892 $sections = core_course_external
::get_course_contents($course->id
, array());
893 // We need to execute the return values cleaning process to simulate the web service server.
894 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
896 $modinfo = get_fast_modinfo($course);
898 foreach ($sections[0]['modules'] as $module) {
899 if ($module['id'] == $forumcm->id
and $module['modname'] == 'forum') {
900 $cm = $modinfo->cms
[$forumcm->id
];
901 $formattedtext = format_text($cm->content
, FORMAT_HTML
,
902 array('noclean' => true, 'para' => false, 'filter' => false));
903 $this->assertEquals($formattedtext, $module['description']);
904 $this->assertEquals($forumcm->instance
, $module['instance']);
905 $this->assertContains('1 unread post', $module['afterlink']);
906 $testexecuted = $testexecuted +
2;
907 } else if ($module['id'] == $labelcm->id
and $module['modname'] == 'label') {
908 $cm = $modinfo->cms
[$labelcm->id
];
909 $formattedtext = format_text($cm->content
, FORMAT_HTML
,
910 array('noclean' => true, 'para' => false, 'filter' => false));
911 $this->assertEquals($formattedtext, $module['description']);
912 $this->assertEquals($labelcm->instance
, $module['instance']);
913 $testexecuted = $testexecuted +
1;
914 } else if ($module['id'] == $datacm->id
and $module['modname'] == 'data') {
915 $this->assertContains('customcompletionrules', $module['customdata']);
916 $testexecuted = $testexecuted +
1;
919 foreach ($sections[2]['modules'] as $module) {
920 if ($module['id'] == $urlcm->id
and $module['modname'] == 'url') {
921 $this->assertContains('width=100,height=100', $module['onclick']);
922 $testexecuted = $testexecuted +
1;
926 $CFG->forum_allowforcedreadtracking
= 0; // Recover original value.
927 forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests.
929 $this->assertEquals(5, $testexecuted);
930 $this->assertEquals(0, $sections[0]['section']);
932 $this->assertCount(5, $sections[0]['modules']);
933 $this->assertCount(1, $sections[1]['modules']);
934 $this->assertCount(1, $sections[2]['modules']);
935 $this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions.
936 $this->assertCount(1, $sections[4]['modules']); // One module for the hidden section with a visible activity.
937 $this->assertNotEmpty($sections[3]['availabilityinfo']);
938 $this->assertEquals(1, $sections[1]['section']);
939 $this->assertEquals(2, $sections[2]['section']);
940 $this->assertEquals(3, $sections[3]['section']);
941 $this->assertEquals(4, $sections[4]['section']);
942 $this->assertContains('<iframe', $sections[2]['summary']);
943 $this->assertContains('</iframe>', $sections[2]['summary']);
944 $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']);
946 $sections = core_course_external
::get_course_contents($course->id
,
947 array(array("name" => "invalid", "value" => 1)));
948 $this->fail('Exception expected due to invalid option.');
949 } catch (moodle_exception
$e) {
950 $this->assertEquals('errorinvalidparam', $e->errorcode
);
956 * Test get_course_contents as student
958 public function test_get_course_contents_student() {
960 $this->resetAfterTest(true);
962 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
964 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
965 $user = self
::getDataGenerator()->create_user();
966 self
::getDataGenerator()->enrol_user($user->id
, $course->id
, $studentroleid);
967 $this->setUser($user);
969 $sections = core_course_external
::get_course_contents($course->id
, array());
970 // We need to execute the return values cleaning process to simulate the web service server.
971 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
973 $this->assertCount(4, $sections); // Nothing for the not visible section.
974 $this->assertCount(5, $sections[0]['modules']);
975 $this->assertCount(1, $sections[1]['modules']);
976 $this->assertCount(1, $sections[2]['modules']);
977 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
979 $this->assertNotEmpty($sections[3]['availabilityinfo']);
980 $this->assertEquals(1, $sections[1]['section']);
981 $this->assertEquals(2, $sections[2]['section']);
982 $this->assertEquals(3, $sections[3]['section']);
983 // The module with the availability restriction met is returning contents.
984 $this->assertNotEmpty($sections[1]['modules'][0]['contents']);
985 // The module with the availability restriction not met is not returning contents.
986 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]);
988 // Now include flag for returning stealth information (fake section).
989 $sections = core_course_external
::get_course_contents($course->id
,
990 array(array("name" => "includestealthmodules", "value" => 1)));
991 // We need to execute the return values cleaning process to simulate the web service server.
992 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
994 $this->assertCount(5, $sections); // Include fake section with stealth activities.
995 $this->assertCount(5, $sections[0]['modules']);
996 $this->assertCount(1, $sections[1]['modules']);
997 $this->assertCount(1, $sections[2]['modules']);
998 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
999 $this->assertCount(1, $sections[4]['modules']); // One stealh module.
1000 $this->assertEquals(-1, $sections[4]['id']);
1004 * Test get_course_contents excluding modules
1006 public function test_get_course_contents_excluding_modules() {
1007 $this->resetAfterTest(true);
1009 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1011 // Test exclude modules.
1012 $sections = core_course_external
::get_course_contents($course->id
, array(array("name" => "excludemodules", "value" => 1)));
1014 // We need to execute the return values cleaning process to simulate the web service server.
1015 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1017 $this->assertEmpty($sections[0]['modules']);
1018 $this->assertEmpty($sections[1]['modules']);
1022 * Test get_course_contents excluding contents
1024 public function test_get_course_contents_excluding_contents() {
1025 $this->resetAfterTest(true);
1027 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1029 // Test exclude modules.
1030 $sections = core_course_external
::get_course_contents($course->id
, array(array("name" => "excludecontents", "value" => 1)));
1032 // We need to execute the return values cleaning process to simulate the web service server.
1033 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1035 foreach ($sections as $section) {
1036 foreach ($section['modules'] as $module) {
1037 // Only resources return contents.
1038 if (isset($module['contents'])) {
1039 $this->assertEmpty($module['contents']);
1046 * Test get_course_contents filtering by section number
1048 public function test_get_course_contents_section_number() {
1049 $this->resetAfterTest(true);
1051 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1053 // Test exclude modules.
1054 $sections = core_course_external
::get_course_contents($course->id
, array(array("name" => "sectionnumber", "value" => 0)));
1056 // We need to execute the return values cleaning process to simulate the web service server.
1057 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1059 $this->assertCount(1, $sections);
1060 $this->assertCount(5, $sections[0]['modules']);
1064 * Test get_course_contents filtering by cmid
1066 public function test_get_course_contents_cmid() {
1067 $this->resetAfterTest(true);
1069 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1071 // Test exclude modules.
1072 $sections = core_course_external
::get_course_contents($course->id
, array(array("name" => "cmid", "value" => $forumcm->id
)));
1074 // We need to execute the return values cleaning process to simulate the web service server.
1075 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1077 $this->assertCount(4, $sections);
1078 $this->assertCount(1, $sections[0]['modules']);
1079 $this->assertEquals($forumcm->id
, $sections[0]['modules'][0]["id"]);
1084 * Test get_course_contents filtering by cmid and section
1086 public function test_get_course_contents_section_cmid() {
1087 $this->resetAfterTest(true);
1089 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1091 // Test exclude modules.
1092 $sections = core_course_external
::get_course_contents($course->id
, array(
1093 array("name" => "cmid", "value" => $forumcm->id
),
1094 array("name" => "sectionnumber", "value" => 0)
1097 // We need to execute the return values cleaning process to simulate the web service server.
1098 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1100 $this->assertCount(1, $sections);
1101 $this->assertCount(1, $sections[0]['modules']);
1102 $this->assertEquals($forumcm->id
, $sections[0]['modules'][0]["id"]);
1106 * Test get_course_contents filtering by modname
1108 public function test_get_course_contents_modname() {
1109 $this->resetAfterTest(true);
1111 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1113 // Test exclude modules.
1114 $sections = core_course_external
::get_course_contents($course->id
, array(array("name" => "modname", "value" => "forum")));
1116 // We need to execute the return values cleaning process to simulate the web service server.
1117 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1119 $this->assertCount(4, $sections);
1120 $this->assertCount(1, $sections[0]['modules']);
1121 $this->assertEquals($forumcm->id
, $sections[0]['modules'][0]["id"]);
1125 * Test get_course_contents filtering by modname
1127 public function test_get_course_contents_modid() {
1128 $this->resetAfterTest(true);
1130 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1132 // Test exclude modules.
1133 $sections = core_course_external
::get_course_contents($course->id
, array(
1134 array("name" => "modname", "value" => "page"),
1135 array("name" => "modid", "value" => $pagecm->instance
),
1138 // We need to execute the return values cleaning process to simulate the web service server.
1139 $sections = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $sections);
1141 $this->assertCount(4, $sections);
1142 $this->assertCount(1, $sections[0]['modules']);
1143 $this->assertEquals("page", $sections[0]['modules'][0]["modname"]);
1144 $this->assertEquals($pagecm->instance
, $sections[0]['modules'][0]["instance"]);
1148 * Test get course contents completion
1150 public function test_get_course_contents_completion() {
1152 $this->resetAfterTest(true);
1154 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1156 // Test activity not completed yet.
1157 $result = core_course_external
::get_course_contents($course->id
, array(
1158 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance
)));
1159 // We need to execute the return values cleaning process to simulate the web service server.
1160 $result = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $result);
1162 $this->assertCount(1, $result[0]['modules']);
1163 $this->assertEquals("forum", $result[0]['modules'][0]["modname"]);
1164 $this->assertEquals(COMPLETION_TRACKING_MANUAL
, $result[0]['modules'][0]["completion"]);
1165 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']);
1166 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']);
1167 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
1169 // Set activity completed.
1170 core_completion_external
::update_activity_completion_status_manually($forumcm->id
, true);
1172 $result = core_course_external
::get_course_contents($course->id
, array(
1173 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance
)));
1174 // We need to execute the return values cleaning process to simulate the web service server.
1175 $result = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $result);
1177 $this->assertEquals(COMPLETION_COMPLETE
, $result[0]['modules'][0]["completiondata"]['state']);
1178 $this->assertNotEmpty($result[0]['modules'][0]["completiondata"]['timecompleted']);
1179 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
1181 // Disable completion.
1182 $CFG->enablecompletion
= 0;
1183 $result = core_course_external
::get_course_contents($course->id
, array(
1184 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance
)));
1185 // We need to execute the return values cleaning process to simulate the web service server.
1186 $result = external_api
::clean_returnvalue(core_course_external
::get_course_contents_returns(), $result);
1188 $this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]);
1192 * Test duplicate_course
1194 public function test_duplicate_course() {
1195 $this->resetAfterTest(true);
1197 // Create one course with three modules.
1198 $course = self
::getDataGenerator()->create_course();
1199 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id
));
1200 $forumcm = get_coursemodule_from_id('forum', $forum->cmid
);
1201 $forumcontext = context_module
::instance($forum->cmid
);
1202 $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id
));
1203 $datacontext = context_module
::instance($data->cmid
);
1204 $datacm = get_coursemodule_from_instance('page', $data->id
);
1205 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id
));
1206 $pagecontext = context_module
::instance($page->cmid
);
1207 $pagecm = get_coursemodule_from_instance('page', $page->id
);
1209 // Set the required capabilities by the external function.
1210 $coursecontext = context_course
::instance($course->id
);
1211 $categorycontext = context_coursecat
::instance($course->category
);
1212 $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id
);
1213 $this->assignUserCapability('moodle/course:view', $categorycontext->id
, $roleid);
1214 $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id
, $roleid);
1215 $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id
, $roleid);
1216 $this->assignUserCapability('moodle/backup:configure', $coursecontext->id
, $roleid);
1217 // Optional capabilities to copy user data.
1218 $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id
, $roleid);
1219 $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id
, $roleid);
1221 $newcourse['fullname'] = 'Course duplicate';
1222 $newcourse['shortname'] = 'courseduplicate';
1223 $newcourse['categoryid'] = $course->category
;
1224 $newcourse['visible'] = true;
1225 $newcourse['options'][] = array('name' => 'users', 'value' => true);
1227 $duplicate = core_course_external
::duplicate_course($course->id
, $newcourse['fullname'],
1228 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']);
1230 // We need to execute the return values cleaning process to simulate the web service server.
1231 $duplicate = external_api
::clean_returnvalue(core_course_external
::duplicate_course_returns(), $duplicate);
1233 // Check that the course has been duplicated.
1234 $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
1238 * Test update_courses
1240 public function test_update_courses() {
1241 global $DB, $CFG, $USER, $COURSE;
1243 // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this
1244 // trick because we are both updating and getting (for testing) course information
1245 // in the same request and core_course_external::update_courses()
1246 // is overwriting $COURSE all over the time with OLD values, so later
1247 // use of get_course() fetches those OLD values instead of the updated ones.
1248 // See MDL-39723 for more info.
1249 $origcourse = clone($COURSE);
1251 $this->resetAfterTest(true);
1253 // Set the required capabilities by the external function.
1254 $contextid = context_system
::instance()->id
;
1255 $roleid = $this->assignUserCapability('moodle/course:update', $contextid);
1256 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid);
1257 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1258 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1259 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1260 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1261 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
1262 $this->assignUserCapability('moodle/course:viewhiddencourses', $contextid, $roleid);
1263 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid);
1265 // Create category and course.
1266 $category1 = self
::getDataGenerator()->create_category();
1267 $category2 = self
::getDataGenerator()->create_category();
1268 $originalcourse1 = self
::getDataGenerator()->create_course();
1269 self
::getDataGenerator()->enrol_user($USER->id
, $originalcourse1->id
, $roleid);
1270 $originalcourse2 = self
::getDataGenerator()->create_course();
1271 self
::getDataGenerator()->enrol_user($USER->id
, $originalcourse2->id
, $roleid);
1273 // Course values to be updated.
1274 $course1['id'] = $originalcourse1->id
;
1275 $course1['fullname'] = 'Updated test course 1';
1276 $course1['shortname'] = 'Udestedtestcourse1';
1277 $course1['categoryid'] = $category1->id
;
1278 $course2['id'] = $originalcourse2->id
;
1279 $course2['fullname'] = 'Updated test course 2';
1280 $course2['shortname'] = 'Updestedtestcourse2';
1281 $course2['categoryid'] = $category2->id
;
1282 $course2['idnumber'] = 'Updatedidnumber2';
1283 $course2['summary'] = 'Updaated description for course 2';
1284 $course2['summaryformat'] = FORMAT_HTML
;
1285 $course2['format'] = 'topics';
1286 $course2['showgrades'] = 1;
1287 $course2['newsitems'] = 3;
1288 $course2['startdate'] = 1420092000; // 01/01/2015.
1289 $course2['enddate'] = 1422669600; // 01/31/2015.
1290 $course2['maxbytes'] = 100000;
1291 $course2['showreports'] = 1;
1292 $course2['visible'] = 0;
1293 $course2['hiddensections'] = 0;
1294 $course2['groupmode'] = 0;
1295 $course2['groupmodeforce'] = 0;
1296 $course2['defaultgroupingid'] = 0;
1297 $course2['enablecompletion'] = 1;
1298 $course2['lang'] = 'en';
1299 $course2['forcetheme'] = 'bootstrapbase';
1300 $courses = array($course1, $course2);
1302 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1303 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1304 $updatedcoursewarnings);
1305 $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line.
1307 // Check that right number of courses were created.
1308 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1310 // Check that the courses were correctly created.
1311 foreach ($courses as $course) {
1312 $courseinfo = course_get_format($course['id'])->get_course();
1313 if ($course['id'] == $course2['id']) {
1314 $this->assertEquals($course2['fullname'], $courseinfo->fullname
);
1315 $this->assertEquals($course2['shortname'], $courseinfo->shortname
);
1316 $this->assertEquals($course2['categoryid'], $courseinfo->category
);
1317 $this->assertEquals($course2['idnumber'], $courseinfo->idnumber
);
1318 $this->assertEquals($course2['summary'], $courseinfo->summary
);
1319 $this->assertEquals($course2['summaryformat'], $courseinfo->summaryformat
);
1320 $this->assertEquals($course2['format'], $courseinfo->format
);
1321 $this->assertEquals($course2['showgrades'], $courseinfo->showgrades
);
1322 $this->assertEquals($course2['newsitems'], $courseinfo->newsitems
);
1323 $this->assertEquals($course2['startdate'], $courseinfo->startdate
);
1324 $this->assertEquals($course2['enddate'], $courseinfo->enddate
);
1325 $this->assertEquals($course2['maxbytes'], $courseinfo->maxbytes
);
1326 $this->assertEquals($course2['showreports'], $courseinfo->showreports
);
1327 $this->assertEquals($course2['visible'], $courseinfo->visible
);
1328 $this->assertEquals($course2['hiddensections'], $courseinfo->hiddensections
);
1329 $this->assertEquals($course2['groupmode'], $courseinfo->groupmode
);
1330 $this->assertEquals($course2['groupmodeforce'], $courseinfo->groupmodeforce
);
1331 $this->assertEquals($course2['defaultgroupingid'], $courseinfo->defaultgroupingid
);
1332 $this->assertEquals($course2['lang'], $courseinfo->lang
);
1334 if (!empty($CFG->allowcoursethemes
)) {
1335 $this->assertEquals($course2['forcetheme'], $courseinfo->theme
);
1338 $this->assertEquals($course2['enablecompletion'], $courseinfo->enablecompletion
);
1339 } else if ($course['id'] == $course1['id']) {
1340 $this->assertEquals($course1['fullname'], $courseinfo->fullname
);
1341 $this->assertEquals($course1['shortname'], $courseinfo->shortname
);
1342 $this->assertEquals($course1['categoryid'], $courseinfo->category
);
1343 $this->assertEquals(FORMAT_MOODLE
, $courseinfo->summaryformat
);
1344 $this->assertEquals('topics', $courseinfo->format
);
1345 $this->assertEquals(5, course_get_format($course['id'])->get_last_section_number());
1346 $this->assertEquals(0, $courseinfo->newsitems
);
1347 $this->assertEquals(FORMAT_MOODLE
, $courseinfo->summaryformat
);
1349 throw new moodle_exception('Unexpected shortname');
1353 $courses = array($course1);
1354 // Try update course without update capability.
1355 $user = self
::getDataGenerator()->create_user();
1356 $this->setUser($user);
1357 $this->unassignUserCapability('moodle/course:update', $contextid, $roleid);
1358 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1359 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1360 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1361 $updatedcoursewarnings);
1362 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1364 // Try update course category without capability.
1365 $this->assignUserCapability('moodle/course:update', $contextid, $roleid);
1366 $this->unassignUserCapability('moodle/course:changecategory', $contextid, $roleid);
1367 $user = self
::getDataGenerator()->create_user();
1368 $this->setUser($user);
1369 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1370 $course1['categoryid'] = $category2->id
;
1371 $courses = array($course1);
1372 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1373 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1374 $updatedcoursewarnings);
1375 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1377 // Try update course fullname without capability.
1378 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid);
1379 $this->unassignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1380 $user = self
::getDataGenerator()->create_user();
1381 $this->setUser($user);
1382 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1383 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1384 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1385 $updatedcoursewarnings);
1386 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1387 $course1['fullname'] = 'Testing fullname without permission';
1388 $courses = array($course1);
1389 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1390 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1391 $updatedcoursewarnings);
1392 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1394 // Try update course shortname without capability.
1395 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1396 $this->unassignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1397 $user = self
::getDataGenerator()->create_user();
1398 $this->setUser($user);
1399 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1400 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1401 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1402 $updatedcoursewarnings);
1403 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1404 $course1['shortname'] = 'Testing shortname without permission';
1405 $courses = array($course1);
1406 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1407 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1408 $updatedcoursewarnings);
1409 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1411 // Try update course idnumber without capability.
1412 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1413 $this->unassignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1414 $user = self
::getDataGenerator()->create_user();
1415 $this->setUser($user);
1416 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1417 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1418 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1419 $updatedcoursewarnings);
1420 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1421 $course1['idnumber'] = 'NEWIDNUMBER';
1422 $courses = array($course1);
1423 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1424 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1425 $updatedcoursewarnings);
1426 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1428 // Try update course summary without capability.
1429 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1430 $this->unassignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1431 $user = self
::getDataGenerator()->create_user();
1432 $this->setUser($user);
1433 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1434 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1435 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1436 $updatedcoursewarnings);
1437 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1438 $course1['summary'] = 'New summary';
1439 $courses = array($course1);
1440 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1441 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1442 $updatedcoursewarnings);
1443 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1445 // Try update course with invalid summary format.
1446 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1447 $user = self
::getDataGenerator()->create_user();
1448 $this->setUser($user);
1449 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1450 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1451 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1452 $updatedcoursewarnings);
1453 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1454 $course1['summaryformat'] = 10;
1455 $courses = array($course1);
1456 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1457 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1458 $updatedcoursewarnings);
1459 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1461 // Try update course visibility without capability.
1462 $this->unassignUserCapability('moodle/course:visibility', $contextid, $roleid);
1463 $user = self
::getDataGenerator()->create_user();
1464 $this->setUser($user);
1465 self
::getDataGenerator()->enrol_user($user->id
, $course1['id'], $roleid);
1466 $course1['summaryformat'] = FORMAT_MOODLE
;
1467 $courses = array($course1);
1468 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1469 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1470 $updatedcoursewarnings);
1471 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1472 $course1['visible'] = 0;
1473 $courses = array($course1);
1474 $updatedcoursewarnings = core_course_external
::update_courses($courses);
1475 $updatedcoursewarnings = external_api
::clean_returnvalue(core_course_external
::update_courses_returns(),
1476 $updatedcoursewarnings);
1477 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1481 * Test delete course_module.
1483 public function test_delete_modules() {
1486 // Ensure we reset the data after this test.
1487 $this->resetAfterTest(true);
1490 $user = self
::getDataGenerator()->create_user();
1492 // Set the tests to run as the user.
1493 self
::setUser($user);
1495 // Create a course to add the modules.
1496 $course = self
::getDataGenerator()->create_course();
1498 // Create two test modules.
1499 $record = new stdClass();
1500 $record->course
= $course->id
;
1501 $module1 = self
::getDataGenerator()->create_module('forum', $record);
1502 $module2 = self
::getDataGenerator()->create_module('assign', $record);
1504 // Check the forum was correctly created.
1505 $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id
)));
1507 // Check the assignment was correctly created.
1508 $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id
)));
1510 // Check data exists in the course modules table.
1511 $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
1512 array('module1' => $module1->cmid
, 'module2' => $module2->cmid
)));
1514 // Enrol the user in the course.
1515 $enrol = enrol_get_plugin('manual');
1516 $enrolinstances = enrol_get_instances($course->id
, true);
1517 foreach ($enrolinstances as $courseenrolinstance) {
1518 if ($courseenrolinstance->enrol
== "manual") {
1519 $instance = $courseenrolinstance;
1523 $enrol->enrol_user($instance, $user->id
);
1525 // Assign capabilities to delete module 1.
1526 $modcontext = context_module
::instance($module1->cmid
);
1527 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id
);
1529 // Assign capabilities to delete module 2.
1530 $modcontext = context_module
::instance($module2->cmid
);
1531 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1532 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id
, $newrole);
1534 // Deleting these module instances.
1535 core_course_external
::delete_modules(array($module1->cmid
, $module2->cmid
));
1537 // Check the forum was deleted.
1538 $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id
)));
1540 // Check the assignment was deleted.
1541 $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id
)));
1543 // Check we retrieve no data in the course modules table.
1544 $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
1545 array('module1' => $module1->cmid
, 'module2' => $module2->cmid
)));
1547 // Call with non-existent course module id and ensure exception thrown.
1549 core_course_external
::delete_modules(array('1337'));
1550 $this->fail('Exception expected due to missing course module.');
1551 } catch (dml_missing_record_exception
$e) {
1552 $this->assertEquals('invalidcoursemodule', $e->errorcode
);
1555 // Create two modules.
1556 $module1 = self
::getDataGenerator()->create_module('forum', $record);
1557 $module2 = self
::getDataGenerator()->create_module('assign', $record);
1559 // Since these modules were recreated the user will not have capabilities
1560 // to delete them, ensure exception is thrown if they try.
1562 core_course_external
::delete_modules(array($module1->cmid
, $module2->cmid
));
1563 $this->fail('Exception expected due to missing capability.');
1564 } catch (moodle_exception
$e) {
1565 $this->assertEquals('nopermissions', $e->errorcode
);
1568 // Unenrol user from the course.
1569 $enrol->unenrol_user($instance, $user->id
);
1571 // Try and delete modules from the course the user was unenrolled in, make sure exception thrown.
1573 core_course_external
::delete_modules(array($module1->cmid
, $module2->cmid
));
1574 $this->fail('Exception expected due to being unenrolled from the course.');
1575 } catch (moodle_exception
$e) {
1576 $this->assertEquals('requireloginerror', $e->errorcode
);
1581 * Test import_course into an empty course
1583 public function test_import_course_empty() {
1586 $this->resetAfterTest(true);
1588 $course1 = self
::getDataGenerator()->create_course();
1589 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id
, 'name' => 'Forum test'));
1590 $page = $this->getDataGenerator()->create_module('page', array('course' => $course1->id
, 'name' => 'Page test'));
1592 $course2 = self
::getDataGenerator()->create_course();
1594 $course1cms = get_fast_modinfo($course1->id
)->get_cms();
1595 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1597 // Verify the state of the courses before we do the import.
1598 $this->assertCount(2, $course1cms);
1599 $this->assertEmpty($course2cms);
1601 // Setup the user to run the operation (ugly hack because validate_context() will
1602 // fail as the email is not set by $this->setAdminUser()).
1603 $this->setAdminUser();
1604 $USER->email
= 'emailtopass@example.com';
1606 // Import from course1 to course2.
1607 core_course_external
::import_course($course1->id
, $course2->id
, 0);
1609 // Verify that now we have two modules in both courses.
1610 $course1cms = get_fast_modinfo($course1->id
)->get_cms();
1611 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1612 $this->assertCount(2, $course1cms);
1613 $this->assertCount(2, $course2cms);
1615 // Verify that the names transfered across correctly.
1616 foreach ($course2cms as $cm) {
1617 if ($cm->modname
=== 'page') {
1618 $this->assertEquals($cm->name
, $page->name
);
1619 } else if ($cm->modname
=== 'forum') {
1620 $this->assertEquals($cm->name
, $forum->name
);
1622 $this->fail('Unknown CM found.');
1628 * Test import_course into an filled course
1630 public function test_import_course_filled() {
1633 $this->resetAfterTest(true);
1635 // Add forum and page to course1.
1636 $course1 = self
::getDataGenerator()->create_course();
1637 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id
, 'name' => 'Forum test'));
1638 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id
, 'name' => 'Page test'));
1640 // Add quiz to course 2.
1641 $course2 = self
::getDataGenerator()->create_course();
1642 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id
, 'name' => 'Page test'));
1644 $course1cms = get_fast_modinfo($course1->id
)->get_cms();
1645 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1647 // Verify the state of the courses before we do the import.
1648 $this->assertCount(2, $course1cms);
1649 $this->assertCount(1, $course2cms);
1651 // Setup the user to run the operation (ugly hack because validate_context() will
1652 // fail as the email is not set by $this->setAdminUser()).
1653 $this->setAdminUser();
1654 $USER->email
= 'emailtopass@example.com';
1656 // Import from course1 to course2 without deleting content.
1657 core_course_external
::import_course($course1->id
, $course2->id
, 0);
1659 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1661 // Verify that now we have three modules in course2.
1662 $this->assertCount(3, $course2cms);
1664 // Verify that the names transfered across correctly.
1665 foreach ($course2cms as $cm) {
1666 if ($cm->modname
=== 'page') {
1667 $this->assertEquals($cm->name
, $page->name
);
1668 } else if ($cm->modname
=== 'forum') {
1669 $this->assertEquals($cm->name
, $forum->name
);
1670 } else if ($cm->modname
=== 'quiz') {
1671 $this->assertEquals($cm->name
, $quiz->name
);
1673 $this->fail('Unknown CM found.');
1679 * Test import_course with only blocks set to backup
1681 public function test_import_course_blocksonly() {
1684 $this->resetAfterTest(true);
1686 // Add forum and page to course1.
1687 $course1 = self
::getDataGenerator()->create_course();
1688 $course1ctx = context_course
::instance($course1->id
);
1689 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id
, 'name' => 'Forum test'));
1690 $block = $this->getDataGenerator()->create_block('online_users', array('parentcontextid' => $course1ctx->id
));
1692 $course2 = self
::getDataGenerator()->create_course();
1693 $course2ctx = context_course
::instance($course2->id
);
1694 $initialblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id
));
1695 $initialcmcount = count(get_fast_modinfo($course2->id
)->get_cms());
1697 // Setup the user to run the operation (ugly hack because validate_context() will
1698 // fail as the email is not set by $this->setAdminUser()).
1699 $this->setAdminUser();
1700 $USER->email
= 'emailtopass@example.com';
1702 // Import from course1 to course2 without deleting content, but excluding
1705 array('name' => 'activities', 'value' => 0),
1706 array('name' => 'blocks', 'value' => 1),
1707 array('name' => 'filters', 'value' => 0),
1710 core_course_external
::import_course($course1->id
, $course2->id
, 0, $options);
1712 $newcmcount = count(get_fast_modinfo($course2->id
)->get_cms());
1713 $newblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id
));
1714 // Check that course modules haven't changed, but that blocks have.
1715 $this->assertEquals($initialcmcount, $newcmcount);
1716 $this->assertEquals(($initialblockcount +
1), $newblockcount);
1720 * Test import_course into an filled course, deleting content.
1722 public function test_import_course_deletecontent() {
1724 $this->resetAfterTest(true);
1726 // Add forum and page to course1.
1727 $course1 = self
::getDataGenerator()->create_course();
1728 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id
, 'name' => 'Forum test'));
1729 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id
, 'name' => 'Page test'));
1731 // Add quiz to course 2.
1732 $course2 = self
::getDataGenerator()->create_course();
1733 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id
, 'name' => 'Page test'));
1735 $course1cms = get_fast_modinfo($course1->id
)->get_cms();
1736 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1738 // Verify the state of the courses before we do the import.
1739 $this->assertCount(2, $course1cms);
1740 $this->assertCount(1, $course2cms);
1742 // Setup the user to run the operation (ugly hack because validate_context() will
1743 // fail as the email is not set by $this->setAdminUser()).
1744 $this->setAdminUser();
1745 $USER->email
= 'emailtopass@example.com';
1747 // Import from course1 to course2, deleting content.
1748 core_course_external
::import_course($course1->id
, $course2->id
, 1);
1750 $course2cms = get_fast_modinfo($course2->id
)->get_cms();
1752 // Verify that now we have two modules in course2.
1753 $this->assertCount(2, $course2cms);
1755 // Verify that the course only contains the imported modules.
1756 foreach ($course2cms as $cm) {
1757 if ($cm->modname
=== 'page') {
1758 $this->assertEquals($cm->name
, $page->name
);
1759 } else if ($cm->modname
=== 'forum') {
1760 $this->assertEquals($cm->name
, $forum->name
);
1762 $this->fail('Unknown CM found: '.$cm->name
);
1768 * Ensure import_course handles incorrect deletecontent option correctly.
1770 public function test_import_course_invalid_deletecontent_option() {
1771 $this->resetAfterTest(true);
1773 $course1 = self
::getDataGenerator()->create_course();
1774 $course2 = self
::getDataGenerator()->create_course();
1776 $this->expectException('moodle_exception');
1777 $this->expectExceptionMessage(get_string('invalidextparam', 'webservice', -1));
1778 // Import from course1 to course2, with invalid option
1779 core_course_external
::import_course($course1->id
, $course2->id
, -1);;
1783 * Test view_course function
1785 public function test_view_course() {
1787 $this->resetAfterTest();
1789 // Course without sections.
1790 $course = $this->getDataGenerator()->create_course(array('numsections' => 5), array('createsections' => true));
1791 $this->setAdminUser();
1793 // Redirect events to the sink, so we can recover them later.
1794 $sink = $this->redirectEvents();
1796 $result = core_course_external
::view_course($course->id
, 1);
1797 $result = external_api
::clean_returnvalue(core_course_external
::view_course_returns(), $result);
1798 $events = $sink->get_events();
1799 $event = reset($events);
1801 // Check the event details are correct.
1802 $this->assertInstanceOf('\core\event\course_viewed', $event);
1803 $this->assertEquals(context_course
::instance($course->id
), $event->get_context());
1804 $this->assertEquals(1, $event->other
['coursesectionnumber']);
1806 $result = core_course_external
::view_course($course->id
);
1807 $result = external_api
::clean_returnvalue(core_course_external
::view_course_returns(), $result);
1808 $events = $sink->get_events();
1809 $event = array_pop($events);
1812 // Check the event details are correct.
1813 $this->assertInstanceOf('\core\event\course_viewed', $event);
1814 $this->assertEquals(context_course
::instance($course->id
), $event->get_context());
1815 $this->assertEmpty($event->other
);
1820 * Test get_course_module
1822 public function test_get_course_module() {
1825 $this->resetAfterTest(true);
1827 $this->setAdminUser();
1828 $course = self
::getDataGenerator()->create_course();
1830 'course' => $course->id
,
1831 'name' => 'First Assignment'
1834 'idnumber' => 'ABC',
1838 $assign = self
::getDataGenerator()->create_module('assign', $record, $options);
1840 $outcomescale = 'Distinction, Very Good, Good, Pass, Fail';
1842 // Insert a custom grade scale to be used by an outcome.
1843 $gradescale = new grade_scale();
1844 $gradescale->name
= 'gettcoursemodulescale';
1845 $gradescale->courseid
= $course->id
;
1846 $gradescale->userid
= 0;
1847 $gradescale->scale
= $outcomescale;
1848 $gradescale->description
= 'This scale is used to mark standard assignments.';
1849 $gradescale->insert();
1851 // Insert an outcome.
1852 $data = new stdClass();
1853 $data->courseid
= $course->id
;
1854 $data->fullname
= 'Team work';
1855 $data->shortname
= 'Team work';
1856 $data->scaleid
= $gradescale->id
;
1857 $outcome = new grade_outcome($data, false);
1860 $outcomegradeitem = new grade_item();
1861 $outcomegradeitem->itemname
= $outcome->shortname
;
1862 $outcomegradeitem->itemtype
= 'mod';
1863 $outcomegradeitem->itemmodule
= 'assign';
1864 $outcomegradeitem->iteminstance
= $assign->id
;
1865 $outcomegradeitem->outcomeid
= $outcome->id
;
1866 $outcomegradeitem->cmid
= 0;
1867 $outcomegradeitem->courseid
= $course->id
;
1868 $outcomegradeitem->aggregationcoef
= 0;
1869 $outcomegradeitem->itemnumber
= 1; // The activity's original grade item will be 0.
1870 $outcomegradeitem->gradetype
= GRADE_TYPE_SCALE
;
1871 $outcomegradeitem->scaleid
= $outcome->scaleid
;
1872 $outcomegradeitem->insert();
1874 $assignmentgradeitem = grade_item
::fetch(
1876 'itemtype' => 'mod',
1877 'itemmodule' => 'assign',
1878 'iteminstance' => $assign->id
,
1880 'courseid' => $course->id
1883 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid
);
1884 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder
);
1886 // Test admin user can see the complete hidden activity.
1887 $result = core_course_external
::get_course_module($assign->cmid
);
1888 $result = external_api
::clean_returnvalue(core_course_external
::get_course_module_returns(), $result);
1890 $this->assertCount(0, $result['warnings']);
1891 // Test we retrieve all the fields.
1892 $this->assertCount(28, $result['cm']);
1893 $this->assertEquals($record['name'], $result['cm']['name']);
1894 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
1895 $this->assertEquals(100, $result['cm']['grade']);
1896 $this->assertEquals(0.0, $result['cm']['gradepass']);
1897 $this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']);
1898 $this->assertEmpty($result['cm']['advancedgrading'][0]['method']);
1899 $this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']);
1901 $student = $this->getDataGenerator()->create_user();
1902 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1904 self
::getDataGenerator()->enrol_user($student->id
, $course->id
, $studentrole->id
);
1905 $this->setUser($student);
1907 // The user shouldn't be able to see the activity.
1909 core_course_external
::get_course_module($assign->cmid
);
1910 $this->fail('Exception expected due to invalid permissions.');
1911 } catch (moodle_exception
$e) {
1912 $this->assertEquals('requireloginerror', $e->errorcode
);
1915 // Make module visible.
1916 set_coursemodule_visible($assign->cmid
, 1);
1918 // Test student user.
1919 $result = core_course_external
::get_course_module($assign->cmid
);
1920 $result = external_api
::clean_returnvalue(core_course_external
::get_course_module_returns(), $result);
1922 $this->assertCount(0, $result['warnings']);
1923 // Test we retrieve only the few files we can see.
1924 $this->assertCount(11, $result['cm']);
1925 $this->assertEquals($assign->cmid
, $result['cm']['id']);
1926 $this->assertEquals($course->id
, $result['cm']['course']);
1927 $this->assertEquals('assign', $result['cm']['modname']);
1928 $this->assertEquals($assign->id
, $result['cm']['instance']);
1933 * Test get_course_module_by_instance
1935 public function test_get_course_module_by_instance() {
1938 $this->resetAfterTest(true);
1940 $this->setAdminUser();
1941 $course = self
::getDataGenerator()->create_course();
1943 'course' => $course->id
,
1944 'name' => 'First quiz',
1948 'idnumber' => 'ABC',
1952 $quiz = self
::getDataGenerator()->create_module('quiz', $record, $options);
1954 // Test admin user can see the complete hidden activity.
1955 $result = core_course_external
::get_course_module_by_instance('quiz', $quiz->id
);
1956 $result = external_api
::clean_returnvalue(core_course_external
::get_course_module_by_instance_returns(), $result);
1958 $this->assertCount(0, $result['warnings']);
1959 // Test we retrieve all the fields.
1960 $this->assertCount(26, $result['cm']);
1961 $this->assertEquals($record['name'], $result['cm']['name']);
1962 $this->assertEquals($record['grade'], $result['cm']['grade']);
1963 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
1965 $student = $this->getDataGenerator()->create_user();
1966 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1968 self
::getDataGenerator()->enrol_user($student->id
, $course->id
, $studentrole->id
);
1969 $this->setUser($student);
1971 // The user shouldn't be able to see the activity.
1973 core_course_external
::get_course_module_by_instance('quiz', $quiz->id
);
1974 $this->fail('Exception expected due to invalid permissions.');
1975 } catch (moodle_exception
$e) {
1976 $this->assertEquals('requireloginerror', $e->errorcode
);
1979 // Make module visible.
1980 set_coursemodule_visible($quiz->cmid
, 1);
1982 // Test student user.
1983 $result = core_course_external
::get_course_module_by_instance('quiz', $quiz->id
);
1984 $result = external_api
::clean_returnvalue(core_course_external
::get_course_module_by_instance_returns(), $result);
1986 $this->assertCount(0, $result['warnings']);
1987 // Test we retrieve only the few files we can see.
1988 $this->assertCount(11, $result['cm']);
1989 $this->assertEquals($quiz->cmid
, $result['cm']['id']);
1990 $this->assertEquals($course->id
, $result['cm']['course']);
1991 $this->assertEquals('quiz', $result['cm']['modname']);
1992 $this->assertEquals($quiz->id
, $result['cm']['instance']);
1994 // Try with an invalid module name.
1996 core_course_external
::get_course_module_by_instance('abc', $quiz->id
);
1997 $this->fail('Exception expected due to invalid module name.');
1998 } catch (dml_read_exception
$e) {
1999 $this->assertEquals('dmlreadexception', $e->errorcode
);
2005 * Test get_activities_overview
2007 public function test_get_activities_overview() {
2010 $this->resetAfterTest();
2011 $course1 = self
::getDataGenerator()->create_course();
2012 $course2 = self
::getDataGenerator()->create_course();
2014 // Create a viewer user.
2015 $viewer = self
::getDataGenerator()->create_user((object) array('trackforums' => 1));
2016 $this->getDataGenerator()->enrol_user($viewer->id
, $course1->id
);
2017 $this->getDataGenerator()->enrol_user($viewer->id
, $course2->id
);
2019 // Create two forums - one in each course.
2020 $record = new stdClass();
2021 $record->course
= $course1->id
;
2022 $forum1 = self
::getDataGenerator()->create_module('forum', (object) array('course' => $course1->id
));
2023 $forum2 = self
::getDataGenerator()->create_module('forum', (object) array('course' => $course2->id
));
2025 $this->setAdminUser();
2026 // A standard post in the forum.
2027 $record = new stdClass();
2028 $record->course
= $course1->id
;
2029 $record->userid
= $USER->id
;
2030 $record->forum
= $forum1->id
;
2031 $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2033 $this->setUser($viewer->id
);
2034 $courses = array($course1->id
, $course2->id
);
2036 $result = core_course_external
::get_activities_overview($courses);
2037 $this->assertDebuggingCalledCount(8);
2038 $result = external_api
::clean_returnvalue(core_course_external
::get_activities_overview_returns(), $result);
2040 // There should be one entry for course1, and no others.
2041 $this->assertCount(1, $result['courses']);
2042 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2043 // Check expected overview data for the module.
2044 $this->assertEquals('forum', $result['courses'][0]['overviews'][0]['module']);
2045 $this->assertContains('1 total unread', $result['courses'][0]['overviews'][0]['overviewtext']);
2049 * Test get_user_navigation_options
2051 public function test_get_user_navigation_options() {
2054 $this->resetAfterTest();
2055 $course1 = self
::getDataGenerator()->create_course();
2056 $course2 = self
::getDataGenerator()->create_course();
2058 // Create a viewer user.
2059 $viewer = self
::getDataGenerator()->create_user();
2060 $this->getDataGenerator()->enrol_user($viewer->id
, $course1->id
);
2061 $this->getDataGenerator()->enrol_user($viewer->id
, $course2->id
);
2063 $this->setUser($viewer->id
);
2064 $courses = array($course1->id
, $course2->id
, SITEID
);
2066 $result = core_course_external
::get_user_navigation_options($courses);
2067 $result = external_api
::clean_returnvalue(core_course_external
::get_user_navigation_options_returns(), $result);
2069 $this->assertCount(0, $result['warnings']);
2070 $this->assertCount(3, $result['courses']);
2072 foreach ($result['courses'] as $course) {
2073 $navoptions = new stdClass
;
2074 foreach ($course['options'] as $option) {
2075 $navoptions->{$option['name']} = $option['available'];
2077 $this->assertCount(9, $course['options']);
2078 if ($course['id'] == SITEID
) {
2079 $this->assertTrue($navoptions->blogs
);
2080 $this->assertFalse($navoptions->notes
);
2081 $this->assertFalse($navoptions->participants
);
2082 $this->assertTrue($navoptions->badges
);
2083 $this->assertTrue($navoptions->tags
);
2084 $this->assertFalse($navoptions->grades
);
2085 $this->assertFalse($navoptions->search
);
2086 $this->assertTrue($navoptions->calendar
);
2087 $this->assertTrue($navoptions->competencies
);
2089 $this->assertTrue($navoptions->blogs
);
2090 $this->assertFalse($navoptions->notes
);
2091 $this->assertTrue($navoptions->participants
);
2092 $this->assertTrue($navoptions->badges
);
2093 $this->assertFalse($navoptions->tags
);
2094 $this->assertTrue($navoptions->grades
);
2095 $this->assertFalse($navoptions->search
);
2096 $this->assertFalse($navoptions->calendar
);
2097 $this->assertTrue($navoptions->competencies
);
2103 * Test get_user_administration_options
2105 public function test_get_user_administration_options() {
2108 $this->resetAfterTest();
2109 $course1 = self
::getDataGenerator()->create_course();
2110 $course2 = self
::getDataGenerator()->create_course();
2112 // Create a viewer user.
2113 $viewer = self
::getDataGenerator()->create_user();
2114 $this->getDataGenerator()->enrol_user($viewer->id
, $course1->id
);
2115 $this->getDataGenerator()->enrol_user($viewer->id
, $course2->id
);
2117 $this->setUser($viewer->id
);
2118 $courses = array($course1->id
, $course2->id
, SITEID
);
2120 $result = core_course_external
::get_user_administration_options($courses);
2121 $result = external_api
::clean_returnvalue(core_course_external
::get_user_administration_options_returns(), $result);
2123 $this->assertCount(0, $result['warnings']);
2124 $this->assertCount(3, $result['courses']);
2126 foreach ($result['courses'] as $course) {
2127 $adminoptions = new stdClass
;
2128 foreach ($course['options'] as $option) {
2129 $adminoptions->{$option['name']} = $option['available'];
2131 if ($course['id'] == SITEID
) {
2132 $this->assertCount(16, $course['options']);
2133 $this->assertFalse($adminoptions->update
);
2134 $this->assertFalse($adminoptions->filters
);
2135 $this->assertFalse($adminoptions->reports
);
2136 $this->assertFalse($adminoptions->backup
);
2137 $this->assertFalse($adminoptions->restore
);
2138 $this->assertFalse($adminoptions->files
);
2139 $this->assertFalse(!isset($adminoptions->tags
));
2140 $this->assertFalse($adminoptions->gradebook
);
2141 $this->assertFalse($adminoptions->outcomes
);
2142 $this->assertFalse($adminoptions->badges
);
2143 $this->assertFalse($adminoptions->import
);
2144 $this->assertFalse($adminoptions->publish
);
2145 $this->assertFalse($adminoptions->reset
);
2146 $this->assertFalse($adminoptions->roles
);
2147 $this->assertFalse($adminoptions->editcompletion
);
2149 $this->assertCount(15, $course['options']);
2150 $this->assertFalse($adminoptions->update
);
2151 $this->assertFalse($adminoptions->filters
);
2152 $this->assertFalse($adminoptions->reports
);
2153 $this->assertFalse($adminoptions->backup
);
2154 $this->assertFalse($adminoptions->restore
);
2155 $this->assertFalse($adminoptions->files
);
2156 $this->assertFalse($adminoptions->tags
);
2157 $this->assertFalse($adminoptions->gradebook
);
2158 $this->assertFalse($adminoptions->outcomes
);
2159 $this->assertTrue($adminoptions->badges
);
2160 $this->assertFalse($adminoptions->import
);
2161 $this->assertFalse($adminoptions->publish
);
2162 $this->assertFalse($adminoptions->reset
);
2163 $this->assertFalse($adminoptions->roles
);
2164 $this->assertFalse($adminoptions->editcompletion
);
2170 * Test get_courses_by_fields
2172 public function test_get_courses_by_field() {
2174 $this->resetAfterTest(true);
2176 $category1 = self
::getDataGenerator()->create_category(array('name' => 'Cat 1'));
2177 $category2 = self
::getDataGenerator()->create_category(array('parent' => $category1->id
));
2178 $course1 = self
::getDataGenerator()->create_course(
2179 array('category' => $category1->id
, 'shortname' => 'c1', 'format' => 'topics'));
2180 $course2 = self
::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id
, 'idnumber' => 'i2'));
2182 $student1 = self
::getDataGenerator()->create_user();
2183 $user1 = self
::getDataGenerator()->create_user();
2184 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2185 self
::getDataGenerator()->enrol_user($student1->id
, $course1->id
, $studentrole->id
);
2186 self
::getDataGenerator()->enrol_user($student1->id
, $course2->id
, $studentrole->id
);
2188 self
::setAdminUser();
2189 // As admins, we should be able to retrieve everything.
2190 $result = core_course_external
::get_courses_by_field();
2191 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2192 $this->assertCount(3, $result['courses']);
2193 // Expect to receive all the fields.
2194 $this->assertCount(37, $result['courses'][0]);
2195 $this->assertCount(38, $result['courses'][1]); // One more field because is not the site course.
2196 $this->assertCount(38, $result['courses'][2]); // One more field because is not the site course.
2198 $result = core_course_external
::get_courses_by_field('id', $course1->id
);
2199 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2200 $this->assertCount(1, $result['courses']);
2201 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2202 // Expect to receive all the fields.
2203 $this->assertCount(38, $result['courses'][0]);
2204 // Check default values for course format topics.
2205 $this->assertCount(2, $result['courses'][0]['courseformatoptions']);
2206 foreach ($result['courses'][0]['courseformatoptions'] as $option) {
2207 if ($option['name'] == 'hiddensections') {
2208 $this->assertEquals(0, $option['value']);
2210 $this->assertEquals('coursedisplay', $option['name']);
2211 $this->assertEquals(0, $option['value']);
2215 $result = core_course_external
::get_courses_by_field('id', $course2->id
);
2216 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2217 $this->assertCount(1, $result['courses']);
2218 $this->assertEquals($course2->id
, $result['courses'][0]['id']);
2220 $result = core_course_external
::get_courses_by_field('ids', "$course1->id,$course2->id");
2221 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2222 $this->assertCount(2, $result['courses']);
2224 // Check default filters.
2225 $this->assertCount(3, $result['courses'][0]['filters']);
2226 $this->assertCount(3, $result['courses'][1]['filters']);
2228 $result = core_course_external
::get_courses_by_field('category', $category1->id
);
2229 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2230 $this->assertCount(1, $result['courses']);
2231 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2232 $this->assertEquals('Cat 1', $result['courses'][0]['categoryname']);
2234 $result = core_course_external
::get_courses_by_field('shortname', 'c1');
2235 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2236 $this->assertCount(1, $result['courses']);
2237 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2239 $result = core_course_external
::get_courses_by_field('idnumber', 'i2');
2240 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2241 $this->assertCount(1, $result['courses']);
2242 $this->assertEquals($course2->id
, $result['courses'][0]['id']);
2244 $result = core_course_external
::get_courses_by_field('idnumber', 'x');
2245 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2246 $this->assertCount(0, $result['courses']);
2248 // Change filter value.
2249 filter_set_local_state('mediaplugin', context_course
::instance($course1->id
)->id
, TEXTFILTER_OFF
);
2251 self
::setUser($student1);
2252 // All visible courses (including front page) for normal student.
2253 $result = core_course_external
::get_courses_by_field();
2254 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2255 $this->assertCount(2, $result['courses']);
2256 $this->assertCount(30, $result['courses'][0]);
2257 $this->assertCount(31, $result['courses'][1]); // One field more (course format options), not present in site course.
2259 $result = core_course_external
::get_courses_by_field('id', $course1->id
);
2260 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2261 $this->assertCount(1, $result['courses']);
2262 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2263 // Expect to receive all the files that a student can see.
2264 $this->assertCount(31, $result['courses'][0]);
2266 // Check default filters.
2267 $filters = $result['courses'][0]['filters'];
2268 $this->assertCount(3, $filters);
2270 foreach ($filters as $filter) {
2271 if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF
) {
2275 $this->assertTrue($found);
2277 // Course 2 is not visible.
2278 $result = core_course_external
::get_courses_by_field('id', $course2->id
);
2279 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2280 $this->assertCount(0, $result['courses']);
2282 $result = core_course_external
::get_courses_by_field('ids', "$course1->id,$course2->id");
2283 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2284 $this->assertCount(1, $result['courses']);
2286 $result = core_course_external
::get_courses_by_field('category', $category1->id
);
2287 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2288 $this->assertCount(1, $result['courses']);
2289 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2291 $result = core_course_external
::get_courses_by_field('shortname', 'c1');
2292 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2293 $this->assertCount(1, $result['courses']);
2294 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2296 $result = core_course_external
::get_courses_by_field('idnumber', 'i2');
2297 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2298 $this->assertCount(0, $result['courses']);
2300 $result = core_course_external
::get_courses_by_field('idnumber', 'x');
2301 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2302 $this->assertCount(0, $result['courses']);
2304 self
::setUser($user1);
2305 // All visible courses (including front page) for authenticated user.
2306 $result = core_course_external
::get_courses_by_field();
2307 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2308 $this->assertCount(2, $result['courses']);
2309 $this->assertCount(30, $result['courses'][0]); // Site course.
2310 $this->assertCount(13, $result['courses'][1]); // Only public information, not enrolled.
2312 $result = core_course_external
::get_courses_by_field('id', $course1->id
);
2313 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2314 $this->assertCount(1, $result['courses']);
2315 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2316 // Expect to receive all the files that a authenticated can see.
2317 $this->assertCount(13, $result['courses'][0]);
2319 // Course 2 is not visible.
2320 $result = core_course_external
::get_courses_by_field('id', $course2->id
);
2321 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2322 $this->assertCount(0, $result['courses']);
2324 $result = core_course_external
::get_courses_by_field('ids', "$course1->id,$course2->id");
2325 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2326 $this->assertCount(1, $result['courses']);
2328 $result = core_course_external
::get_courses_by_field('category', $category1->id
);
2329 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2330 $this->assertCount(1, $result['courses']);
2331 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2333 $result = core_course_external
::get_courses_by_field('shortname', 'c1');
2334 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2335 $this->assertCount(1, $result['courses']);
2336 $this->assertEquals($course1->id
, $result['courses'][0]['id']);
2338 $result = core_course_external
::get_courses_by_field('idnumber', 'i2');
2339 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2340 $this->assertCount(0, $result['courses']);
2342 $result = core_course_external
::get_courses_by_field('idnumber', 'x');
2343 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2344 $this->assertCount(0, $result['courses']);
2347 public function test_get_courses_by_field_invalid_field() {
2348 $this->expectException('invalid_parameter_exception');
2349 $result = core_course_external
::get_courses_by_field('zyx', 'x');
2352 public function test_get_courses_by_field_invalid_courses() {
2353 $result = core_course_external
::get_courses_by_field('id', '-1');
2354 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2355 $this->assertCount(0, $result['courses']);
2359 * Test get_courses_by_field_invalid_theme_and_lang
2361 public function test_get_courses_by_field_invalid_theme_and_lang() {
2362 $this->resetAfterTest(true);
2363 $this->setAdminUser();
2365 $course = self
::getDataGenerator()->create_course(array('theme' => 'kkt', 'lang' => 'kkl'));
2366 $result = core_course_external
::get_courses_by_field('id', $course->id
);
2367 $result = external_api
::clean_returnvalue(core_course_external
::get_courses_by_field_returns(), $result);
2368 $this->assertEmpty($result['courses']['0']['theme']);
2369 $this->assertEmpty($result['courses']['0']['lang']);
2373 public function test_check_updates() {
2375 $this->resetAfterTest(true);
2376 $this->setAdminUser();
2378 // Create different types of activities.
2379 $course = self
::getDataGenerator()->create_course();
2380 $tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz',
2381 'resource', 'scorm', 'survey', 'url', 'wiki');
2384 foreach ($tocreate as $modname) {
2385 $modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id
));
2386 $modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid
);
2387 $modules[$modname]['context'] = context_module
::instance($modules[$modname]['instance']->cmid
);
2390 $student = self
::getDataGenerator()->create_user();
2391 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2392 self
::getDataGenerator()->enrol_user($student->id
, $course->id
, $studentrole->id
);
2393 $this->setUser($student);
2396 $this->waitForSecond();
2398 foreach ($modules as $modname => $data) {
2399 $params[$data['cm']->id
] = array(
2400 'contextlevel' => 'module',
2401 'id' => $data['cm']->id
,
2406 // Check there is nothing updated because modules are fresh new.
2407 $result = core_course_external
::check_updates($course->id
, $params);
2408 $result = external_api
::clean_returnvalue(core_course_external
::check_updates_returns(), $result);
2409 $this->assertCount(0, $result['instances']);
2410 $this->assertCount(0, $result['warnings']);
2412 // Test with get_updates_since the same data.
2413 $result = core_course_external
::get_updates_since($course->id
, $since);
2414 $result = external_api
::clean_returnvalue(core_course_external
::get_updates_since_returns(), $result);
2415 $this->assertCount(0, $result['instances']);
2416 $this->assertCount(0, $result['warnings']);
2418 // Update a module after a second.
2419 $this->waitForSecond();
2420 set_coursemodule_name($modules['forum']['cm']->id
, 'New forum name');
2423 $result = core_course_external
::check_updates($course->id
, $params);
2424 $result = external_api
::clean_returnvalue(core_course_external
::check_updates_returns(), $result);
2425 $this->assertCount(1, $result['instances']);
2426 $this->assertCount(0, $result['warnings']);
2427 foreach ($result['instances'] as $module) {
2428 foreach ($module['updates'] as $update) {
2429 if ($module['id'] == $modules['forum']['cm']->id
and $update['name'] == 'configuration') {
2434 $this->assertTrue($found);
2436 // Test with get_updates_since the same data.
2437 $result = core_course_external
::get_updates_since($course->id
, $since);
2438 $result = external_api
::clean_returnvalue(core_course_external
::get_updates_since_returns(), $result);
2439 $this->assertCount(1, $result['instances']);
2440 $this->assertCount(0, $result['warnings']);
2442 $this->assertCount(1, $result['instances']);
2443 $this->assertCount(0, $result['warnings']);
2444 foreach ($result['instances'] as $module) {
2445 foreach ($module['updates'] as $update) {
2446 if ($module['id'] == $modules['forum']['cm']->id
and $update['name'] == 'configuration') {
2451 $this->assertTrue($found);
2453 // Do not retrieve the configuration field.
2454 $filter = array('files');
2456 $result = core_course_external
::check_updates($course->id
, $params, $filter);
2457 $result = external_api
::clean_returnvalue(core_course_external
::check_updates_returns(), $result);
2458 $this->assertCount(0, $result['instances']);
2459 $this->assertCount(0, $result['warnings']);
2460 $this->assertFalse($found);
2462 // Add invalid cmid.
2464 'contextlevel' => 'module',
2468 $result = core_course_external
::check_updates($course->id
, $params);
2469 $result = external_api
::clean_returnvalue(core_course_external
::check_updates_returns(), $result);
2470 $this->assertCount(1, $result['warnings']);
2471 $this->assertEquals(-2, $result['warnings'][0]['itemid']);
2475 * Test cases for the get_enrolled_courses_by_timeline_classification test.
2477 public function get_get_enrolled_courses_by_timeline_classification_test_cases() {
2483 'shortname' => 'apast',
2484 'startdate' => $now - ($day * 2),
2485 'enddate' => $now - $day
2488 'shortname' => 'bpast',
2489 'startdate' => $now - ($day * 2),
2490 'enddate' => $now - $day
2493 'shortname' => 'cpast',
2494 'startdate' => $now - ($day * 2),
2495 'enddate' => $now - $day
2498 'shortname' => 'dpast',
2499 'startdate' => $now - ($day * 2),
2500 'enddate' => $now - $day
2503 'shortname' => 'epast',
2504 'startdate' => $now - ($day * 2),
2505 'enddate' => $now - $day
2508 'shortname' => 'ainprogress',
2509 'startdate' => $now - $day,
2510 'enddate' => $now +
$day
2513 'shortname' => 'binprogress',
2514 'startdate' => $now - $day,
2515 'enddate' => $now +
$day
2518 'shortname' => 'cinprogress',
2519 'startdate' => $now - $day,
2520 'enddate' => $now +
$day
2523 'shortname' => 'dinprogress',
2524 'startdate' => $now - $day,
2525 'enddate' => $now +
$day
2528 'shortname' => 'einprogress',
2529 'startdate' => $now - $day,
2530 'enddate' => $now +
$day
2533 'shortname' => 'afuture',
2534 'startdate' => $now +
$day
2537 'shortname' => 'bfuture',
2538 'startdate' => $now +
$day
2541 'shortname' => 'cfuture',
2542 'startdate' => $now +
$day
2545 'shortname' => 'dfuture',
2546 'startdate' => $now +
$day
2549 'shortname' => 'efuture',
2550 'startdate' => $now +
$day
2554 // Raw enrolled courses result set should be returned in this order:
2555 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
2556 // dfuture, dinprogress, dpast, efuture, einprogress, epast
2558 // By classification the offset values for each record should be:
2559 // COURSE_TIMELINE_FUTURE
2560 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
2561 // COURSE_TIMELINE_INPROGRESS
2562 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
2563 // COURSE_TIMELINE_PAST
2564 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
2566 // NOTE: The offset applies to the unfiltered full set of courses before the classification
2567 // filtering is done.
2568 // E.g. In our example if an offset of 2 is given then it would mean the first
2569 // two courses (afuture, ainprogress) are ignored.
2573 'classification' => 'future',
2576 'expectedcourses' => [],
2577 'expectednextoffset' => 0
2579 // COURSE_TIMELINE_FUTURE.
2580 'future not limit no offset' => [
2581 'coursedata' => $coursedata,
2582 'classification' => 'future',
2585 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2586 'expectednextoffset' => 15
2588 'future no offset' => [
2589 'coursedata' => $coursedata,
2590 'classification' => 'future',
2593 'expectedcourses' => ['afuture', 'bfuture'],
2594 'expectednextoffset' => 4
2596 'future offset' => [
2597 'coursedata' => $coursedata,
2598 'classification' => 'future',
2601 'expectedcourses' => ['bfuture', 'cfuture'],
2602 'expectednextoffset' => 7
2604 'future exact limit' => [
2605 'coursedata' => $coursedata,
2606 'classification' => 'future',
2609 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2610 'expectednextoffset' => 13
2612 'future limit less results' => [
2613 'coursedata' => $coursedata,
2614 'classification' => 'future',
2617 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2618 'expectednextoffset' => 15
2620 'future limit less results with offset' => [
2621 'coursedata' => $coursedata,
2622 'classification' => 'future',
2625 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
2626 'expectednextoffset' => 15
2628 'all no limit or offset' => [
2629 'coursedata' => $coursedata,
2630 'classification' => 'all',
2633 'expectedcourses' => [
2650 'expectednextoffset' => 15
2652 'all limit no offset' => [
2653 'coursedata' => $coursedata,
2654 'classification' => 'all',
2657 'expectedcourses' => [
2664 'expectednextoffset' => 5
2666 'all limit and offset' => [
2667 'coursedata' => $coursedata,
2668 'classification' => 'all',
2671 'expectedcourses' => [
2678 'expectednextoffset' => 10
2680 'all offset past result set' => [
2681 'coursedata' => $coursedata,
2682 'classification' => 'all',
2685 'expectedcourses' => [],
2686 'expectednextoffset' => 50
2692 * Test the get_enrolled_courses_by_timeline_classification function.
2694 * @dataProvider get_get_enrolled_courses_by_timeline_classification_test_cases()
2695 * @param array $coursedata Courses to create
2696 * @param string $classification Timeline classification
2697 * @param int $limit Maximum number of results
2698 * @param int $offset Offset the unfiltered courses result set by this amount
2699 * @param array $expectedcourses Expected courses in result
2700 * @param int $expectednextoffset Expected next offset value in result
2702 public function test_get_enrolled_courses_by_timeline_classification(
2710 $this->resetAfterTest();
2711 $generator = $this->getDataGenerator();
2713 $courses = array_map(function($coursedata) use ($generator) {
2714 return $generator->create_course($coursedata);
2717 $student = $generator->create_user();
2719 foreach ($courses as $course) {
2720 $generator->enrol_user($student->id
, $course->id
, 'student');
2723 $this->setUser($student);
2725 // NOTE: The offset applies to the unfiltered full set of courses before the classification
2726 // filtering is done.
2727 // E.g. In our example if an offset of 2 is given then it would mean the first
2728 // two courses (afuture, ainprogress) are ignored.
2729 $result = core_course_external
::get_enrolled_courses_by_timeline_classification(
2735 $result = external_api
::clean_returnvalue(
2736 core_course_external
::get_enrolled_courses_by_timeline_classification_returns(),
2740 $actual = array_map(function($course) {
2741 return $course['shortname'];
2742 }, $result['courses']);
2744 $this->assertEquals($expectedcourses, $actual);
2745 $this->assertEquals($expectednextoffset, $result['nextoffset']);
2749 * Test the get_recent_courses function.
2751 public function test_get_recent_courses() {
2754 $this->resetAfterTest();
2755 $generator = $this->getDataGenerator();
2757 set_config('hiddenuserfields', 'lastaccess');
2760 for ($i = 1; $i < 12; $i++
) {
2761 $courses[] = $generator->create_course();
2764 $student = $generator->create_user();
2765 $teacher = $generator->create_user();
2767 foreach ($courses as $course) {
2768 $generator->enrol_user($student->id
, $course->id
, 'student');
2771 $generator->enrol_user($teacher->id
, $courses[0]->id
, 'teacher');
2773 $this->setUser($student);
2775 $result = core_course_external
::get_recent_courses($USER->id
);
2777 // No course accessed.
2778 $this->assertCount(0, $result);
2780 foreach ($courses as $course) {
2781 core_course_external
::view_course($course->id
);
2784 // Every course accessed.
2785 $result = core_course_external
::get_recent_courses($USER->id
);
2786 $this->assertCount( 11, $result);
2788 // Every course accessed, result limited to 10 courses.
2789 $result = core_course_external
::get_recent_courses($USER->id
, 10);
2790 $this->assertCount(10, $result);
2792 $guestcourse = $generator->create_course(
2793 (object)array('shortname' => 'guestcourse',
2794 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED
,
2795 'enrol_guest_password_0' => ''));
2796 core_course_external
::view_course($guestcourse->id
);
2798 // Every course accessed, even the not enrolled one.
2799 $result = core_course_external
::get_recent_courses($USER->id
);
2800 $this->assertCount(12, $result);
2802 // Offset 5, return 7 out of 12.
2803 $result = core_course_external
::get_recent_courses($USER->id
, 0, 5);
2804 $this->assertCount(7, $result);
2806 // Offset 5 and limit 3, return 3 out of 12.
2807 $result = core_course_external
::get_recent_courses($USER->id
, 3, 5);
2808 $this->assertCount(3, $result);
2810 // Sorted by course id ASC.
2811 $result = core_course_external
::get_recent_courses($USER->id
, 0, 0, 'id ASC');
2812 $this->assertEquals($courses[0]->id
, array_shift($result)->id
);
2814 // Sorted by course id DESC.
2815 $result = core_course_external
::get_recent_courses($USER->id
, 0, 0, 'id DESC');
2816 $this->assertEquals($guestcourse->id
, array_shift($result)->id
);
2818 // If last access is hidden, only get the courses where has viewhiddenuserfields capability.
2819 $this->setUser($teacher);
2820 $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
2821 $usercontext = context_user
::instance($student->id
);
2822 $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid);
2824 // Sorted by course id DESC.
2825 $result = core_course_external
::get_recent_courses($student->id
);
2826 $this->assertCount(1, $result);
2827 $this->assertEquals($courses[0]->id
, array_shift($result)->id
);