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