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