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