MDL-67264 core_course: Activity chooser new feature
[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']);
967 $testexecuted = $testexecuted + 1;
968 }
969 }
970
716c103d
JL
971 $CFG->forum_allowforcedreadtracking = 0; // Recover original value.
972 forum_tp_count_forum_unread_posts($forumcm, $course, true); // Reset static cache for further tests.
973
1206a487 974 $this->assertEquals(5, $testexecuted);
935429e2 975 $this->assertEquals(0, $sections[0]['section']);
fb695f6e 976
10b88bf2 977 $this->assertCount(5, $sections[0]['modules']);
935429e2
JL
978 $this->assertCount(1, $sections[1]['modules']);
979 $this->assertCount(1, $sections[2]['modules']);
10b88bf2
JL
980 $this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions.
981 $this->assertCount(1, $sections[4]['modules']); // One module for the hidden section with a visible activity.
935429e2
JL
982 $this->assertNotEmpty($sections[3]['availabilityinfo']);
983 $this->assertEquals(1, $sections[1]['section']);
984 $this->assertEquals(2, $sections[2]['section']);
985 $this->assertEquals(3, $sections[3]['section']);
10b88bf2 986 $this->assertEquals(4, $sections[4]['section']);
935429e2
JL
987 $this->assertContains('<iframe', $sections[2]['summary']);
988 $this->assertContains('</iframe>', $sections[2]['summary']);
935429e2 989 $this->assertNotEmpty($sections[2]['modules'][0]['availabilityinfo']);
8a5346a7
JL
990 try {
991 $sections = core_course_external::get_course_contents($course->id,
992 array(array("name" => "invalid", "value" => 1)));
993 $this->fail('Exception expected due to invalid option.');
994 } catch (moodle_exception $e) {
995 $this->assertEquals('errorinvalidparam', $e->errorcode);
996 }
997 }
998
999
10b88bf2
JL
1000 /**
1001 * Test get_course_contents as student
1002 */
1003 public function test_get_course_contents_student() {
1004 global $DB;
1005 $this->resetAfterTest(true);
1006
1007 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1008
1009 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
1010 $user = self::getDataGenerator()->create_user();
1011 self::getDataGenerator()->enrol_user($user->id, $course->id, $studentroleid);
1012 $this->setUser($user);
1013
1014 $sections = core_course_external::get_course_contents($course->id, array());
1015 // We need to execute the return values cleaning process to simulate the web service server.
1016 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1017
1018 $this->assertCount(4, $sections); // Nothing for the not visible section.
1019 $this->assertCount(5, $sections[0]['modules']);
1020 $this->assertCount(1, $sections[1]['modules']);
1021 $this->assertCount(1, $sections[2]['modules']);
1022 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
1023
1024 $this->assertNotEmpty($sections[3]['availabilityinfo']);
1025 $this->assertEquals(1, $sections[1]['section']);
1026 $this->assertEquals(2, $sections[2]['section']);
1027 $this->assertEquals(3, $sections[3]['section']);
1028 // The module with the availability restriction met is returning contents.
1029 $this->assertNotEmpty($sections[1]['modules'][0]['contents']);
1030 // The module with the availability restriction not met is not returning contents.
1031 $this->assertArrayNotHasKey('contents', $sections[2]['modules'][0]);
1032
1033 // Now include flag for returning stealth information (fake section).
1034 $sections = core_course_external::get_course_contents($course->id,
1035 array(array("name" => "includestealthmodules", "value" => 1)));
1036 // We need to execute the return values cleaning process to simulate the web service server.
1037 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1038
1039 $this->assertCount(5, $sections); // Include fake section with stealth activities.
1040 $this->assertCount(5, $sections[0]['modules']);
1041 $this->assertCount(1, $sections[1]['modules']);
1042 $this->assertCount(1, $sections[2]['modules']);
1043 $this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
1044 $this->assertCount(1, $sections[4]['modules']); // One stealh module.
1045 $this->assertEquals(-1, $sections[4]['id']);
1046 }
1047
8a5346a7
JL
1048 /**
1049 * Test get_course_contents excluding modules
1050 */
1051 public function test_get_course_contents_excluding_modules() {
1052 $this->resetAfterTest(true);
1053
1054 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1055
1056 // Test exclude modules.
1057 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludemodules", "value" => 1)));
1058
1059 // We need to execute the return values cleaning process to simulate the web service server.
1060 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1061
935429e2
JL
1062 $this->assertEmpty($sections[0]['modules']);
1063 $this->assertEmpty($sections[1]['modules']);
8a5346a7
JL
1064 }
1065
1066 /**
1067 * Test get_course_contents excluding contents
1068 */
1069 public function test_get_course_contents_excluding_contents() {
1070 $this->resetAfterTest(true);
1071
1072 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1073
1074 // Test exclude modules.
1075 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "excludecontents", "value" => 1)));
1076
1077 // We need to execute the return values cleaning process to simulate the web service server.
1078 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1079
1080 foreach ($sections as $section) {
1081 foreach ($section['modules'] as $module) {
1082 // Only resources return contents.
1083 if (isset($module['contents'])) {
1084 $this->assertEmpty($module['contents']);
1085 }
1086 }
1087 }
1088 }
1089
1090 /**
1091 * Test get_course_contents filtering by section number
1092 */
1093 public function test_get_course_contents_section_number() {
1094 $this->resetAfterTest(true);
1095
1096 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1097
1098 // Test exclude modules.
1099 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "sectionnumber", "value" => 0)));
1100
1101 // We need to execute the return values cleaning process to simulate the web service server.
1102 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1103
1104 $this->assertCount(1, $sections);
10b88bf2 1105 $this->assertCount(5, $sections[0]['modules']);
8a5346a7
JL
1106 }
1107
1108 /**
1109 * Test get_course_contents filtering by cmid
1110 */
1111 public function test_get_course_contents_cmid() {
1112 $this->resetAfterTest(true);
1113
1114 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1115
1116 // Test exclude modules.
1117 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "cmid", "value" => $forumcm->id)));
1118
1119 // We need to execute the return values cleaning process to simulate the web service server.
1120 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1121
935429e2 1122 $this->assertCount(4, $sections);
8a5346a7
JL
1123 $this->assertCount(1, $sections[0]['modules']);
1124 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
1125 }
1126
1127
1128 /**
1129 * Test get_course_contents filtering by cmid and section
1130 */
1131 public function test_get_course_contents_section_cmid() {
1132 $this->resetAfterTest(true);
1133
1134 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1135
1136 // Test exclude modules.
1137 $sections = core_course_external::get_course_contents($course->id, array(
1138 array("name" => "cmid", "value" => $forumcm->id),
1139 array("name" => "sectionnumber", "value" => 0)
1140 ));
1141
1142 // We need to execute the return values cleaning process to simulate the web service server.
1143 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1144
1145 $this->assertCount(1, $sections);
1146 $this->assertCount(1, $sections[0]['modules']);
1147 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
1148 }
1149
1150 /**
1151 * Test get_course_contents filtering by modname
1152 */
1153 public function test_get_course_contents_modname() {
1154 $this->resetAfterTest(true);
1155
1156 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1157
1158 // Test exclude modules.
1159 $sections = core_course_external::get_course_contents($course->id, array(array("name" => "modname", "value" => "forum")));
1160
1161 // We need to execute the return values cleaning process to simulate the web service server.
1162 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1163
935429e2 1164 $this->assertCount(4, $sections);
8a5346a7
JL
1165 $this->assertCount(1, $sections[0]['modules']);
1166 $this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
1167 }
1168
1169 /**
1170 * Test get_course_contents filtering by modname
1171 */
1172 public function test_get_course_contents_modid() {
1173 $this->resetAfterTest(true);
1174
1175 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
1176
1177 // Test exclude modules.
1178 $sections = core_course_external::get_course_contents($course->id, array(
1179 array("name" => "modname", "value" => "page"),
1180 array("name" => "modid", "value" => $pagecm->instance),
1181 ));
1182
1183 // We need to execute the return values cleaning process to simulate the web service server.
1184 $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
1185
935429e2 1186 $this->assertCount(4, $sections);
8a5346a7
JL
1187 $this->assertCount(1, $sections[0]['modules']);
1188 $this->assertEquals("page", $sections[0]['modules'][0]["modname"]);
1189 $this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]);
2a7a0216
JM
1190 }
1191
1de51367
JL
1192 /**
1193 * Test get course contents completion
1194 */
1195 public function test_get_course_contents_completion() {
1196 global $CFG;
1197 $this->resetAfterTest(true);
1198
1199 list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
76724712 1200 availability_completion\condition::wipe_static_cache();
1de51367
JL
1201
1202 // Test activity not completed yet.
1203 $result = core_course_external::get_course_contents($course->id, array(
1204 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance)));
1205 // We need to execute the return values cleaning process to simulate the web service server.
1206 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1207
1208 $this->assertCount(1, $result[0]['modules']);
1209 $this->assertEquals("forum", $result[0]['modules'][0]["modname"]);
1210 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]);
1211 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']);
1212 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']);
1213 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
76724712 1214 $this->assertFalse($result[0]['modules'][0]["completiondata"]['valueused']);
1de51367
JL
1215
1216 // Set activity completed.
1217 core_completion_external::update_activity_completion_status_manually($forumcm->id, true);
1218
1219 $result = core_course_external::get_course_contents($course->id, array(
1220 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance)));
1221 // We need to execute the return values cleaning process to simulate the web service server.
1222 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1223
1224 $this->assertEquals(COMPLETION_COMPLETE, $result[0]['modules'][0]["completiondata"]['state']);
1225 $this->assertNotEmpty($result[0]['modules'][0]["completiondata"]['timecompleted']);
1226 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
1227
76724712
MJ
1228 // Test activity with completion value that is used in an availability condition.
1229 $result = core_course_external::get_course_contents($course->id, array(
1230 array("name" => "modname", "value" => "label"), array("name" => "modid", "value" => $labelcm->instance)));
1231 // We need to execute the return values cleaning process to simulate the web service server.
1232 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1233
1234 $this->assertCount(1, $result[0]['modules']);
1235 $this->assertEquals("label", $result[0]['modules'][0]["modname"]);
1236 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]);
1237 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']);
1238 $this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']);
1239 $this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
1240 $this->assertTrue($result[0]['modules'][0]["completiondata"]['valueused']);
1241
1de51367
JL
1242 // Disable completion.
1243 $CFG->enablecompletion = 0;
1244 $result = core_course_external::get_course_contents($course->id, array(
1245 array("name" => "modname", "value" => "forum"), array("name" => "modid", "value" => $forumcm->instance)));
1246 // We need to execute the return values cleaning process to simulate the web service server.
1247 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1248
1249 $this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]);
1250 }
1251
34ad1a01
JL
1252 /**
1253 * Test mimetype is returned for resources with showtype set.
1254 */
1255 public function test_get_course_contents_including_mimetype() {
1256 $this->resetAfterTest(true);
1257
1258 $this->setAdminUser();
1259 $course = self::getDataGenerator()->create_course();
1260
1261 $record = new stdClass();
1262 $record->course = $course->id;
1263 $record->showtype = 1;
1264 $resource = self::getDataGenerator()->create_module('resource', $record);
1265
1266 $result = core_course_external::get_course_contents($course->id);
1267 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1268 $this->assertCount(1, $result[0]['modules']); // One module, first section.
1269 $customdata = unserialize(json_decode($result[0]['modules'][0]['customdata']));
1270 $this->assertEquals('text/plain', $customdata['filedetails']['mimetype']);
1271 }
1272
9b8aed89
JL
1273 /**
1274 * Test contents info is returned.
1275 */
1276 public function test_get_course_contents_contentsinfo() {
1277 global $USER;
1278
1279 $this->resetAfterTest(true);
9b8aed89 1280 $this->setAdminUser();
0ba8114b
AN
1281 $timenow = time();
1282
9b8aed89
JL
1283 $course = self::getDataGenerator()->create_course();
1284
1285 $record = new stdClass();
1286 $record->course = $course->id;
1287 // One resource with one file.
1288 $resource1 = self::getDataGenerator()->create_module('resource', $record);
1289
9b8aed89
JL
1290 // More type of files.
1291 $record->files = file_get_unused_draft_itemid();
1292 $usercontext = context_user::instance($USER->id);
1293 $extensions = array('txt', 'png', 'pdf');
acfd5e83 1294 $fs = get_file_storage();
9b8aed89
JL
1295 foreach ($extensions as $key => $extension) {
1296 // Add actual file there.
1297 $filerecord = array('component' => 'user', 'filearea' => 'draft',
1298 'contextid' => $usercontext->id, 'itemid' => $record->files,
1299 'filename' => 'resource' . $key . '.' . $extension, 'filepath' => '/');
9b8aed89
JL
1300 $fs->create_file_from_string($filerecord, 'Test resource ' . $key . ' file');
1301 }
1302
acfd5e83
JL
1303 // Create file reference.
1304 $repos = repository::get_instances(array('type' => 'user'));
1305 $userrepository = reset($repos);
1306
1307 // Create a user private file.
1308 $userfilerecord = new stdClass;
1309 $userfilerecord->contextid = $usercontext->id;
1310 $userfilerecord->component = 'user';
1311 $userfilerecord->filearea = 'private';
1312 $userfilerecord->itemid = 0;
1313 $userfilerecord->filepath = '/';
1314 $userfilerecord->filename = 'userfile.txt';
1315 $userfilerecord->source = 'test';
1316 $userfile = $fs->create_file_from_string($userfilerecord, 'User file content');
1317 $userfileref = $fs->pack_reference($userfilerecord);
1318
1319 // Clone latest "normal" file.
1320 $filerefrecord = clone (object) $filerecord;
1321 $filerefrecord->filename = 'testref.txt';
1322 $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref);
1323 // Set main file pointing to the file reference.
1324 file_set_sortorder($usercontext->id, 'user', 'draft', $record->files, $filerefrecord->filepath,
1325 $filerefrecord->filename, 1);
1326
1327 // Once the reference has been created, create the file resource.
9b8aed89
JL
1328 $resource2 = self::getDataGenerator()->create_module('resource', $record);
1329
1330 $result = core_course_external::get_course_contents($course->id);
1331 $result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
1332 $this->assertCount(2, $result[0]['modules']);
1333 foreach ($result[0]['modules'] as $module) {
1334 if ($module['instance'] == $resource1->id) {
1335 $this->assertEquals(1, $module['contentsinfo']['filescount']);
1336 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']);
1337 $this->assertEquals($module['contents'][0]['filesize'], $module['contentsinfo']['filessize']);
1338 $this->assertEquals(array('text/plain'), $module['contentsinfo']['mimetypes']);
1339 } else {
acfd5e83 1340 $this->assertEquals(count($extensions) + 1, $module['contentsinfo']['filescount']);
9b8aed89 1341 $filessize = $module['contents'][0]['filesize'] + $module['contents'][1]['filesize'] +
acfd5e83 1342 $module['contents'][2]['filesize'] + $module['contents'][3]['filesize'];
9b8aed89 1343 $this->assertEquals($filessize, $module['contentsinfo']['filessize']);
acfd5e83 1344 $this->assertEquals('user', $module['contentsinfo']['repositorytype']);
9b8aed89
JL
1345 $this->assertGreaterThanOrEqual($timenow, $module['contentsinfo']['lastmodified']);
1346 $this->assertEquals(array('text/plain', 'image/png', 'application/pdf'), $module['contentsinfo']['mimetypes']);
1347 }
1348 }
1349 }
1350
2a7a0216
JM
1351 /**
1352 * Test duplicate_course
1353 */
1354 public function test_duplicate_course() {
1355 $this->resetAfterTest(true);
1356
1357 // Create one course with three modules.
1358 $course = self::getDataGenerator()->create_course();
1359 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
1360 $forumcm = get_coursemodule_from_id('forum', $forum->cmid);
1361 $forumcontext = context_module::instance($forum->cmid);
1362 $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id));
1363 $datacontext = context_module::instance($data->cmid);
1364 $datacm = get_coursemodule_from_instance('page', $data->id);
1365 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
1366 $pagecontext = context_module::instance($page->cmid);
1367 $pagecm = get_coursemodule_from_instance('page', $page->id);
1368
1369 // Set the required capabilities by the external function.
1370 $coursecontext = context_course::instance($course->id);
1371 $categorycontext = context_coursecat::instance($course->category);
1372 $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id);
1373 $this->assignUserCapability('moodle/course:view', $categorycontext->id, $roleid);
1374 $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id, $roleid);
1375 $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id, $roleid);
1376 $this->assignUserCapability('moodle/backup:configure', $coursecontext->id, $roleid);
1377 // Optional capabilities to copy user data.
1378 $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id, $roleid);
1379 $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id, $roleid);
1380
1381 $newcourse['fullname'] = 'Course duplicate';
1382 $newcourse['shortname'] = 'courseduplicate';
1383 $newcourse['categoryid'] = $course->category;
1384 $newcourse['visible'] = true;
1385 $newcourse['options'][] = array('name' => 'users', 'value' => true);
1386
1387 $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'],
1388 $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']);
1389
fb695f6e
JM
1390 // We need to execute the return values cleaning process to simulate the web service server.
1391 $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate);
1392
2a7a0216
JM
1393 // Check that the course has been duplicated.
1394 $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
1395 }
791723c3
RT
1396
1397 /**
1398 * Test update_courses
1399 */
1400 public function test_update_courses() {
a182f88f
EL
1401 global $DB, $CFG, $USER, $COURSE;
1402
1403 // Get current $COURSE to be able to restore it later (defaults to $SITE). We need this
1404 // trick because we are both updating and getting (for testing) course information
1405 // in the same request and core_course_external::update_courses()
1406 // is overwriting $COURSE all over the time with OLD values, so later
1407 // use of get_course() fetches those OLD values instead of the updated ones.
1408 // See MDL-39723 for more info.
1409 $origcourse = clone($COURSE);
791723c3
RT
1410
1411 $this->resetAfterTest(true);
1412
1413 // Set the required capabilities by the external function.
1414 $contextid = context_system::instance()->id;
1415 $roleid = $this->assignUserCapability('moodle/course:update', $contextid);
1416 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid);
7a0162f1 1417 $this->assignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid);
791723c3
RT
1418 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1419 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1420 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1421 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1422 $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
1423 $this->assignUserCapability('moodle/course:viewhiddencourses', $contextid, $roleid);
4a9624af 1424 $this->assignUserCapability('moodle/course:setforcedlanguage', $contextid, $roleid);
791723c3 1425
7a0162f1 1426 // Create category and courses.
791723c3
RT
1427 $category1 = self::getDataGenerator()->create_category();
1428 $category2 = self::getDataGenerator()->create_category();
7a0162f1 1429
791723c3
RT
1430 $originalcourse1 = self::getDataGenerator()->create_course();
1431 self::getDataGenerator()->enrol_user($USER->id, $originalcourse1->id, $roleid);
7a0162f1 1432
791723c3
RT
1433 $originalcourse2 = self::getDataGenerator()->create_course();
1434 self::getDataGenerator()->enrol_user($USER->id, $originalcourse2->id, $roleid);
1435
7a0162f1
DM
1436 // Course with custom fields.
1437 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']);
1438 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text',
1439 'categoryid' => $fieldcategory->get('id'),
1440 'configdata' => ['visibility' => \core_course\customfield\course_handler::VISIBLETOALL, 'locked' => 1]];
1441 $field = self::getDataGenerator()->create_custom_field($customfield);
1442
1443 $originalcourse3 = self::getDataGenerator()->create_course(['customfield_test' => 'Test value']);
1444 self::getDataGenerator()->enrol_user($USER->id, $originalcourse3->id, $roleid);
1445
791723c3
RT
1446 // Course values to be updated.
1447 $course1['id'] = $originalcourse1->id;
1448 $course1['fullname'] = 'Updated test course 1';
1449 $course1['shortname'] = 'Udestedtestcourse1';
1450 $course1['categoryid'] = $category1->id;
7a0162f1 1451
791723c3
RT
1452 $course2['id'] = $originalcourse2->id;
1453 $course2['fullname'] = 'Updated test course 2';
1454 $course2['shortname'] = 'Updestedtestcourse2';
1455 $course2['categoryid'] = $category2->id;
1456 $course2['idnumber'] = 'Updatedidnumber2';
1457 $course2['summary'] = 'Updaated description for course 2';
1458 $course2['summaryformat'] = FORMAT_HTML;
1459 $course2['format'] = 'topics';
1460 $course2['showgrades'] = 1;
1461 $course2['newsitems'] = 3;
1462 $course2['startdate'] = 1420092000; // 01/01/2015.
fbcdb0d7 1463 $course2['enddate'] = 1422669600; // 01/31/2015.
791723c3
RT
1464 $course2['maxbytes'] = 100000;
1465 $course2['showreports'] = 1;
1466 $course2['visible'] = 0;
1467 $course2['hiddensections'] = 0;
1468 $course2['groupmode'] = 0;
1469 $course2['groupmodeforce'] = 0;
1470 $course2['defaultgroupingid'] = 0;
1471 $course2['enablecompletion'] = 1;
1472 $course2['lang'] = 'en';
e00f1c66 1473 $course2['forcetheme'] = 'classic';
7a0162f1
DM
1474
1475 $course3['id'] = $originalcourse3->id;
1476 $updatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'Updated test value'];
1477 $course3['customfields'] = [$updatedcustomfieldvalue];
1478 $courses = array($course1, $course2, $course3);
791723c3
RT
1479
1480 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4 1481 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
7a0162f1 1482 $updatedcoursewarnings);
a182f88f 1483 $COURSE = $origcourse; // Restore $COURSE. Instead of using the OLD one set by the previous line.
791723c3
RT
1484
1485 // Check that right number of courses were created.
1486 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1487
1488 // Check that the courses were correctly created.
1489 foreach ($courses as $course) {
1490 $courseinfo = course_get_format($course['id'])->get_course();
7a0162f1 1491 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course['id']);
791723c3
RT
1492 if ($course['id'] == $course2['id']) {
1493 $this->assertEquals($course2['fullname'], $courseinfo->fullname);
1494 $this->assertEquals($course2['shortname'], $courseinfo->shortname);
1495 $this->assertEquals($course2['categoryid'], $courseinfo->category);
1496 $this->assertEquals($course2['idnumber'], $courseinfo->idnumber);
1497 $this->assertEquals($course2['summary'], $courseinfo->summary);
1498 $this->assertEquals($course2['summaryformat'], $courseinfo->summaryformat);
1499 $this->assertEquals($course2['format'], $courseinfo->format);
1500 $this->assertEquals($course2['showgrades'], $courseinfo->showgrades);
1501 $this->assertEquals($course2['newsitems'], $courseinfo->newsitems);
1502 $this->assertEquals($course2['startdate'], $courseinfo->startdate);
fbcdb0d7 1503 $this->assertEquals($course2['enddate'], $courseinfo->enddate);
791723c3
RT
1504 $this->assertEquals($course2['maxbytes'], $courseinfo->maxbytes);
1505 $this->assertEquals($course2['showreports'], $courseinfo->showreports);
1506 $this->assertEquals($course2['visible'], $courseinfo->visible);
1507 $this->assertEquals($course2['hiddensections'], $courseinfo->hiddensections);
1508 $this->assertEquals($course2['groupmode'], $courseinfo->groupmode);
1509 $this->assertEquals($course2['groupmodeforce'], $courseinfo->groupmodeforce);
1510 $this->assertEquals($course2['defaultgroupingid'], $courseinfo->defaultgroupingid);
1511 $this->assertEquals($course2['lang'], $courseinfo->lang);
1512
1513 if (!empty($CFG->allowcoursethemes)) {
1514 $this->assertEquals($course2['forcetheme'], $courseinfo->theme);
1515 }
1516
8be9cffb 1517 $this->assertEquals($course2['enablecompletion'], $courseinfo->enablecompletion);
7a0162f1 1518 $this->assertEquals(['test' => null], (array)$customfields);
791723c3
RT
1519 } else if ($course['id'] == $course1['id']) {
1520 $this->assertEquals($course1['fullname'], $courseinfo->fullname);
1521 $this->assertEquals($course1['shortname'], $courseinfo->shortname);
1522 $this->assertEquals($course1['categoryid'], $courseinfo->category);
1523 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat);
1524 $this->assertEquals('topics', $courseinfo->format);
89b909f6 1525 $this->assertEquals(5, course_get_format($course['id'])->get_last_section_number());
791723c3
RT
1526 $this->assertEquals(0, $courseinfo->newsitems);
1527 $this->assertEquals(FORMAT_MOODLE, $courseinfo->summaryformat);
7a0162f1
DM
1528 $this->assertEquals(['test' => null], (array)$customfields);
1529 } else if ($course['id'] == $course3['id']) {
1530 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields);
791723c3 1531 } else {
75c597da 1532 throw new moodle_exception('Unexpected shortname');
791723c3
RT
1533 }
1534 }
1535
1536 $courses = array($course1);
1537 // Try update course without update capability.
1538 $user = self::getDataGenerator()->create_user();
1539 $this->setUser($user);
1540 $this->unassignUserCapability('moodle/course:update', $contextid, $roleid);
1541 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1542 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1543 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1544 $updatedcoursewarnings);
791723c3
RT
1545 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1546
1547 // Try update course category without capability.
1548 $this->assignUserCapability('moodle/course:update', $contextid, $roleid);
1549 $this->unassignUserCapability('moodle/course:changecategory', $contextid, $roleid);
1550 $user = self::getDataGenerator()->create_user();
1551 $this->setUser($user);
1552 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1553 $course1['categoryid'] = $category2->id;
1554 $courses = array($course1);
1555 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1556 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1557 $updatedcoursewarnings);
791723c3
RT
1558 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1559
1560 // Try update course fullname without capability.
1561 $this->assignUserCapability('moodle/course:changecategory', $contextid, $roleid);
1562 $this->unassignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1563 $user = self::getDataGenerator()->create_user();
1564 $this->setUser($user);
1565 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1566 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1567 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1568 $updatedcoursewarnings);
791723c3
RT
1569 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1570 $course1['fullname'] = 'Testing fullname without permission';
1571 $courses = array($course1);
1572 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1573 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1574 $updatedcoursewarnings);
791723c3
RT
1575 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1576
1577 // Try update course shortname without capability.
1578 $this->assignUserCapability('moodle/course:changefullname', $contextid, $roleid);
1579 $this->unassignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1580 $user = self::getDataGenerator()->create_user();
1581 $this->setUser($user);
1582 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1583 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1584 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1585 $updatedcoursewarnings);
791723c3
RT
1586 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1587 $course1['shortname'] = 'Testing shortname without permission';
1588 $courses = array($course1);
1589 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1590 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1591 $updatedcoursewarnings);
791723c3
RT
1592 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1593
1594 // Try update course idnumber without capability.
1595 $this->assignUserCapability('moodle/course:changeshortname', $contextid, $roleid);
1596 $this->unassignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1597 $user = self::getDataGenerator()->create_user();
1598 $this->setUser($user);
1599 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1600 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1601 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1602 $updatedcoursewarnings);
791723c3
RT
1603 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1604 $course1['idnumber'] = 'NEWIDNUMBER';
1605 $courses = array($course1);
1606 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1607 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1608 $updatedcoursewarnings);
791723c3
RT
1609 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1610
1611 // Try update course summary without capability.
1612 $this->assignUserCapability('moodle/course:changeidnumber', $contextid, $roleid);
1613 $this->unassignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1614 $user = self::getDataGenerator()->create_user();
1615 $this->setUser($user);
1616 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1617 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1618 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1619 $updatedcoursewarnings);
791723c3
RT
1620 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1621 $course1['summary'] = 'New summary';
1622 $courses = array($course1);
1623 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1624 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1625 $updatedcoursewarnings);
791723c3
RT
1626 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1627
1628 // Try update course with invalid summary format.
1629 $this->assignUserCapability('moodle/course:changesummary', $contextid, $roleid);
1630 $user = self::getDataGenerator()->create_user();
1631 $this->setUser($user);
1632 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1633 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1634 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1635 $updatedcoursewarnings);
791723c3
RT
1636 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1637 $course1['summaryformat'] = 10;
1638 $courses = array($course1);
1639 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1640 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1641 $updatedcoursewarnings);
791723c3
RT
1642 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
1643
1644 // Try update course visibility without capability.
1645 $this->unassignUserCapability('moodle/course:visibility', $contextid, $roleid);
1646 $user = self::getDataGenerator()->create_user();
1647 $this->setUser($user);
1648 self::getDataGenerator()->enrol_user($user->id, $course1['id'], $roleid);
1649 $course1['summaryformat'] = FORMAT_MOODLE;
1650 $courses = array($course1);
1651 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1652 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1653 $updatedcoursewarnings);
791723c3
RT
1654 $this->assertEquals(0, count($updatedcoursewarnings['warnings']));
1655 $course1['visible'] = 0;
1656 $courses = array($course1);
1657 $updatedcoursewarnings = core_course_external::update_courses($courses);
bdf9f4d4
JL
1658 $updatedcoursewarnings = external_api::clean_returnvalue(core_course_external::update_courses_returns(),
1659 $updatedcoursewarnings);
791723c3 1660 $this->assertEquals(1, count($updatedcoursewarnings['warnings']));
7a0162f1
DM
1661
1662 // Try update course custom fields without capability.
1663 $this->unassignUserCapability('moodle/course:changelockedcustomfields', $contextid, $roleid);
1664 $user = self::getDataGenerator()->create_user();
1665 $this->setUser($user);
1666 self::getDataGenerator()->enrol_user($user->id, $course3['id'], $roleid);
1667
1668 $newupdatedcustomfieldvalue = ['shortname' => 'test', 'value' => 'New updated value'];
1669 $course3['customfields'] = [$newupdatedcustomfieldvalue];
1670
1671 core_course_external::update_courses([$course3]);
1672
1673 // Custom field was not updated.
1674 $customfields = \core_course\customfield\course_handler::create()->export_instance_data_object($course3['id']);
1675 $this->assertEquals(['test' => $updatedcustomfieldvalue['value']], (array)$customfields);
791723c3 1676 }
05fc7ccc 1677
79949c1b
MN
1678 /**
1679 * Test delete course_module.
1680 */
1681 public function test_delete_modules() {
1682 global $DB;
1683
1684 // Ensure we reset the data after this test.
1685 $this->resetAfterTest(true);
1686
1687 // Create a user.
1688 $user = self::getDataGenerator()->create_user();
1689
1690 // Set the tests to run as the user.
1691 self::setUser($user);
1692
1693 // Create a course to add the modules.
1694 $course = self::getDataGenerator()->create_course();
1695
1696 // Create two test modules.
1697 $record = new stdClass();
1698 $record->course = $course->id;
1699 $module1 = self::getDataGenerator()->create_module('forum', $record);
40cb4879 1700 $module2 = self::getDataGenerator()->create_module('assign', $record);
79949c1b
MN
1701
1702 // Check the forum was correctly created.
1703 $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id)));
1704
1705 // Check the assignment was correctly created.
40cb4879 1706 $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id)));
79949c1b
MN
1707
1708 // Check data exists in the course modules table.
1709 $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
1710 array('module1' => $module1->cmid, 'module2' => $module2->cmid)));
1711
1712 // Enrol the user in the course.
1713 $enrol = enrol_get_plugin('manual');
1714 $enrolinstances = enrol_get_instances($course->id, true);
1715 foreach ($enrolinstances as $courseenrolinstance) {
1716 if ($courseenrolinstance->enrol == "manual") {
1717 $instance = $courseenrolinstance;
1718 break;
1719 }
1720 }
1721 $enrol->enrol_user($instance, $user->id);
1722
1723 // Assign capabilities to delete module 1.
1724 $modcontext = context_module::instance($module1->cmid);
1725 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id);
1726
1727 // Assign capabilities to delete module 2.
1728 $modcontext = context_module::instance($module2->cmid);
1729 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1730 $this->assignUserCapability('moodle/course:manageactivities', $modcontext->id, $newrole);
1731
1732 // Deleting these module instances.
1733 core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
1734
1735 // Check the forum was deleted.
1736 $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id)));
1737
1738 // Check the assignment was deleted.
40cb4879 1739 $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id)));
79949c1b
MN
1740
1741 // Check we retrieve no data in the course modules table.
1742 $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
1743 array('module1' => $module1->cmid, 'module2' => $module2->cmid)));
1744
1745 // Call with non-existent course module id and ensure exception thrown.
1746 try {
1747 core_course_external::delete_modules(array('1337'));
1748 $this->fail('Exception expected due to missing course module.');
1749 } catch (dml_missing_record_exception $e) {
affdc3b7 1750 $this->assertEquals('invalidcoursemodule', $e->errorcode);
79949c1b
MN
1751 }
1752
1753 // Create two modules.
1754 $module1 = self::getDataGenerator()->create_module('forum', $record);
40cb4879 1755 $module2 = self::getDataGenerator()->create_module('assign', $record);
79949c1b
MN
1756
1757 // Since these modules were recreated the user will not have capabilities
1758 // to delete them, ensure exception is thrown if they try.
1759 try {
1760 core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
1761 $this->fail('Exception expected due to missing capability.');
1762 } catch (moodle_exception $e) {
1763 $this->assertEquals('nopermissions', $e->errorcode);
1764 }
1765
1766 // Unenrol user from the course.
1767 $enrol->unenrol_user($instance, $user->id);
1768
1769 // Try and delete modules from the course the user was unenrolled in, make sure exception thrown.
1770 try {
1771 core_course_external::delete_modules(array($module1->cmid, $module2->cmid));
1772 $this->fail('Exception expected due to being unenrolled from the course.');
1773 } catch (moodle_exception $e) {
1774 $this->assertEquals('requireloginerror', $e->errorcode);
1775 }
1776 }
fce10644
DP
1777
1778 /**
1779 * Test import_course into an empty course
1780 */
1781 public function test_import_course_empty() {
1782 global $USER;
1783
1784 $this->resetAfterTest(true);
1785
1786 $course1 = self::getDataGenerator()->create_course();
1787 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id, 'name' => 'Forum test'));
1788 $page = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 'name' => 'Page test'));
1789
1790 $course2 = self::getDataGenerator()->create_course();
1791
1792 $course1cms = get_fast_modinfo($course1->id)->get_cms();
1793 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1794
1795 // Verify the state of the courses before we do the import.
1796 $this->assertCount(2, $course1cms);
1797 $this->assertEmpty($course2cms);
1798
1799 // Setup the user to run the operation (ugly hack because validate_context() will
1800 // fail as the email is not set by $this->setAdminUser()).
1801 $this->setAdminUser();
0fe86bbd 1802 $USER->email = 'emailtopass@example.com';
fce10644
DP
1803
1804 // Import from course1 to course2.
1805 core_course_external::import_course($course1->id, $course2->id, 0);
1806
1807 // Verify that now we have two modules in both courses.
1808 $course1cms = get_fast_modinfo($course1->id)->get_cms();
1809 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1810 $this->assertCount(2, $course1cms);
1811 $this->assertCount(2, $course2cms);
1812
1813 // Verify that the names transfered across correctly.
1814 foreach ($course2cms as $cm) {
1815 if ($cm->modname === 'page') {
1816 $this->assertEquals($cm->name, $page->name);
1817 } else if ($cm->modname === 'forum') {
1818 $this->assertEquals($cm->name, $forum->name);
1819 } else {
1820 $this->fail('Unknown CM found.');
1821 }
1822 }
fce10644
DP
1823 }
1824
1825 /**
1826 * Test import_course into an filled course
1827 */
1828 public function test_import_course_filled() {
1829 global $USER;
1830
1831 $this->resetAfterTest(true);
1832
1833 // Add forum and page to course1.
1834 $course1 = self::getDataGenerator()->create_course();
1835 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test'));
1836 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test'));
1837
1838 // Add quiz to course 2.
1839 $course2 = self::getDataGenerator()->create_course();
1840 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test'));
1841
1842 $course1cms = get_fast_modinfo($course1->id)->get_cms();
1843 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1844
1845 // Verify the state of the courses before we do the import.
1846 $this->assertCount(2, $course1cms);
1847 $this->assertCount(1, $course2cms);
1848
1849 // Setup the user to run the operation (ugly hack because validate_context() will
1850 // fail as the email is not set by $this->setAdminUser()).
1851 $this->setAdminUser();
0fe86bbd 1852 $USER->email = 'emailtopass@example.com';
fce10644
DP
1853
1854 // Import from course1 to course2 without deleting content.
1855 core_course_external::import_course($course1->id, $course2->id, 0);
1856
1857 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1858
1859 // Verify that now we have three modules in course2.
1860 $this->assertCount(3, $course2cms);
1861
1862 // Verify that the names transfered across correctly.
1863 foreach ($course2cms as $cm) {
1864 if ($cm->modname === 'page') {
1865 $this->assertEquals($cm->name, $page->name);
1866 } else if ($cm->modname === 'forum') {
1867 $this->assertEquals($cm->name, $forum->name);
1868 } else if ($cm->modname === 'quiz') {
1869 $this->assertEquals($cm->name, $quiz->name);
1870 } else {
1871 $this->fail('Unknown CM found.');
1872 }
1873 }
fce10644
DP
1874 }
1875
1876 /**
1877 * Test import_course with only blocks set to backup
1878 */
1879 public function test_import_course_blocksonly() {
1880 global $USER, $DB;
1881
1882 $this->resetAfterTest(true);
1883
1884 // Add forum and page to course1.
1885 $course1 = self::getDataGenerator()->create_course();
1886 $course1ctx = context_course::instance($course1->id);
1887 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test'));
1888 $block = $this->getDataGenerator()->create_block('online_users', array('parentcontextid' => $course1ctx->id));
1889
1890 $course2 = self::getDataGenerator()->create_course();
1891 $course2ctx = context_course::instance($course2->id);
1892 $initialblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id));
1893 $initialcmcount = count(get_fast_modinfo($course2->id)->get_cms());
1894
1895 // Setup the user to run the operation (ugly hack because validate_context() will
1896 // fail as the email is not set by $this->setAdminUser()).
1897 $this->setAdminUser();
0fe86bbd 1898 $USER->email = 'emailtopass@example.com';
fce10644
DP
1899
1900 // Import from course1 to course2 without deleting content, but excluding
1901 // activities.
1902 $options = array(
1903 array('name' => 'activities', 'value' => 0),
1904 array('name' => 'blocks', 'value' => 1),
1905 array('name' => 'filters', 'value' => 0),
1906 );
1907
1908 core_course_external::import_course($course1->id, $course2->id, 0, $options);
1909
1910 $newcmcount = count(get_fast_modinfo($course2->id)->get_cms());
1911 $newblockcount = $DB->count_records('block_instances', array('parentcontextid' => $course2ctx->id));
1912 // Check that course modules haven't changed, but that blocks have.
1913 $this->assertEquals($initialcmcount, $newcmcount);
1914 $this->assertEquals(($initialblockcount + 1), $newblockcount);
fce10644
DP
1915 }
1916
1917 /**
1918 * Test import_course into an filled course, deleting content.
1919 */
1920 public function test_import_course_deletecontent() {
1921 global $USER;
1922 $this->resetAfterTest(true);
1923
1924 // Add forum and page to course1.
1925 $course1 = self::getDataGenerator()->create_course();
1926 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course1->id, 'name' => 'Forum test'));
1927 $page = $this->getDataGenerator()->create_module('page', array('course'=>$course1->id, 'name' => 'Page test'));
1928
1929 // Add quiz to course 2.
1930 $course2 = self::getDataGenerator()->create_course();
1931 $quiz = $this->getDataGenerator()->create_module('quiz', array('course'=>$course2->id, 'name' => 'Page test'));
1932
1933 $course1cms = get_fast_modinfo($course1->id)->get_cms();
1934 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1935
1936 // Verify the state of the courses before we do the import.
1937 $this->assertCount(2, $course1cms);
1938 $this->assertCount(1, $course2cms);
1939
1940 // Setup the user to run the operation (ugly hack because validate_context() will
1941 // fail as the email is not set by $this->setAdminUser()).
1942 $this->setAdminUser();
0fe86bbd 1943 $USER->email = 'emailtopass@example.com';
fce10644
DP
1944
1945 // Import from course1 to course2, deleting content.
1946 core_course_external::import_course($course1->id, $course2->id, 1);
1947
1948 $course2cms = get_fast_modinfo($course2->id)->get_cms();
1949
1950 // Verify that now we have two modules in course2.
1951 $this->assertCount(2, $course2cms);
1952
1953 // Verify that the course only contains the imported modules.
1954 foreach ($course2cms as $cm) {
1955 if ($cm->modname === 'page') {
1956 $this->assertEquals($cm->name, $page->name);
1957 } else if ($cm->modname === 'forum') {
1958 $this->assertEquals($cm->name, $forum->name);
1959 } else {
1960 $this->fail('Unknown CM found: '.$cm->name);
1961 }
1962 }
fce10644
DP
1963 }
1964
1965 /**
1966 * Ensure import_course handles incorrect deletecontent option correctly.
1967 */
1968 public function test_import_course_invalid_deletecontent_option() {
1969 $this->resetAfterTest(true);
1970
1971 $course1 = self::getDataGenerator()->create_course();
1972 $course2 = self::getDataGenerator()->create_course();
1973
52f3e060
RT
1974 $this->expectException('moodle_exception');
1975 $this->expectExceptionMessage(get_string('invalidextparam', 'webservice', -1));
fce10644
DP
1976 // Import from course1 to course2, with invalid option
1977 core_course_external::import_course($course1->id, $course2->id, -1);;
1978 }
e81f67ca
JL
1979
1980 /**
1981 * Test view_course function
1982 */
1983 public function test_view_course() {
1984
1985 $this->resetAfterTest();
1986
1987 // Course without sections.
1988 $course = $this->getDataGenerator()->create_course(array('numsections' => 5), array('createsections' => true));
1989 $this->setAdminUser();
1990
1991 // Redirect events to the sink, so we can recover them later.
1992 $sink = $this->redirectEvents();
1993
bdf9f4d4
JL
1994 $result = core_course_external::view_course($course->id, 1);
1995 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result);
e81f67ca
JL
1996 $events = $sink->get_events();
1997 $event = reset($events);
1998
1999 // Check the event details are correct.
2000 $this->assertInstanceOf('\core\event\course_viewed', $event);
2001 $this->assertEquals(context_course::instance($course->id), $event->get_context());
2002 $this->assertEquals(1, $event->other['coursesectionnumber']);
2003
bdf9f4d4
JL
2004 $result = core_course_external::view_course($course->id);
2005 $result = external_api::clean_returnvalue(core_course_external::view_course_returns(), $result);
e81f67ca
JL
2006 $events = $sink->get_events();
2007 $event = array_pop($events);
2008 $sink->close();
2009
2010 // Check the event details are correct.
2011 $this->assertInstanceOf('\core\event\course_viewed', $event);
2012 $this->assertEquals(context_course::instance($course->id), $event->get_context());
2013 $this->assertEmpty($event->other);
2014
2015 }
c5158499
JL
2016
2017 /**
2018 * Test get_course_module
2019 */
2020 public function test_get_course_module() {
2021 global $DB;
2022
2023 $this->resetAfterTest(true);
2024
2025 $this->setAdminUser();
2026 $course = self::getDataGenerator()->create_course();
2027 $record = array(
2028 'course' => $course->id,
796876b0 2029 'name' => 'First Assignment'
c5158499
JL
2030 );
2031 $options = array(
2032 'idnumber' => 'ABC',
2033 'visible' => 0
2034 );
2035 // Hidden activity.
796876b0 2036 $assign = self::getDataGenerator()->create_module('assign', $record, $options);
c5158499 2037
28ff87be
PFO
2038 $outcomescale = 'Distinction, Very Good, Good, Pass, Fail';
2039
2040 // Insert a custom grade scale to be used by an outcome.
2041 $gradescale = new grade_scale();
2042 $gradescale->name = 'gettcoursemodulescale';
2043 $gradescale->courseid = $course->id;
2044 $gradescale->userid = 0;
2045 $gradescale->scale = $outcomescale;
2046 $gradescale->description = 'This scale is used to mark standard assignments.';
2047 $gradescale->insert();
2048
2049 // Insert an outcome.
2050 $data = new stdClass();
2051 $data->courseid = $course->id;
2052 $data->fullname = 'Team work';
2053 $data->shortname = 'Team work';
2054 $data->scaleid = $gradescale->id;
2055 $outcome = new grade_outcome($data, false);
2056 $outcome->insert();
2057
2058 $outcomegradeitem = new grade_item();
2059 $outcomegradeitem->itemname = $outcome->shortname;
2060 $outcomegradeitem->itemtype = 'mod';
2061 $outcomegradeitem->itemmodule = 'assign';
2062 $outcomegradeitem->iteminstance = $assign->id;
2063 $outcomegradeitem->outcomeid = $outcome->id;
2064 $outcomegradeitem->cmid = 0;
2065 $outcomegradeitem->courseid = $course->id;
2066 $outcomegradeitem->aggregationcoef = 0;
fcc88fdd 2067 $outcomegradeitem->itemnumber = 1000; // Outcomes start at 1000.
28ff87be
PFO
2068 $outcomegradeitem->gradetype = GRADE_TYPE_SCALE;
2069 $outcomegradeitem->scaleid = $outcome->scaleid;
2070 $outcomegradeitem->insert();
2071
2072 $assignmentgradeitem = grade_item::fetch(
2073 array(
2074 'itemtype' => 'mod',
2075 'itemmodule' => 'assign',
2076 'iteminstance' => $assign->id,
2077 'itemnumber' => 0,
2078 'courseid' => $course->id
2079 )
2080 );
2081 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid);
2082 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder);
2083
c5158499 2084 // Test admin user can see the complete hidden activity.
796876b0 2085 $result = core_course_external::get_course_module($assign->cmid);
c5158499
JL
2086 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result);
2087
2088 $this->assertCount(0, $result['warnings']);
2089 // Test we retrieve all the fields.
8341055e 2090 $this->assertCount(28, $result['cm']);
c5158499
JL
2091 $this->assertEquals($record['name'], $result['cm']['name']);
2092 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
796876b0
JL
2093 $this->assertEquals(100, $result['cm']['grade']);
2094 $this->assertEquals(0.0, $result['cm']['gradepass']);
2095 $this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']);
2096 $this->assertEmpty($result['cm']['advancedgrading'][0]['method']);
28ff87be 2097 $this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']);
c5158499
JL
2098
2099 $student = $this->getDataGenerator()->create_user();
2100 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2101
2102 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
2103 $this->setUser($student);
2104
2105 // The user shouldn't be able to see the activity.
2106 try {
796876b0 2107 core_course_external::get_course_module($assign->cmid);
c5158499
JL
2108 $this->fail('Exception expected due to invalid permissions.');
2109 } catch (moodle_exception $e) {
2110 $this->assertEquals('requireloginerror', $e->errorcode);
2111 }
2112
2113 // Make module visible.
796876b0 2114 set_coursemodule_visible($assign->cmid, 1);
c5158499
JL
2115
2116 // Test student user.
796876b0 2117 $result = core_course_external::get_course_module($assign->cmid);
c5158499
JL
2118 $result = external_api::clean_returnvalue(core_course_external::get_course_module_returns(), $result);
2119
2120 $this->assertCount(0, $result['warnings']);
2121 // Test we retrieve only the few files we can see.
2122 $this->assertCount(11, $result['cm']);
796876b0 2123 $this->assertEquals($assign->cmid, $result['cm']['id']);
c5158499 2124 $this->assertEquals($course->id, $result['cm']['course']);
796876b0
JL
2125 $this->assertEquals('assign', $result['cm']['modname']);
2126 $this->assertEquals($assign->id, $result['cm']['instance']);
c5158499
JL
2127
2128 }
13bb6819
JL
2129
2130 /**
2131 * Test get_course_module_by_instance
2132 */
2133 public function test_get_course_module_by_instance() {
2134 global $DB;
2135
2136 $this->resetAfterTest(true);
2137
2138 $this->setAdminUser();
2139 $course = self::getDataGenerator()->create_course();
2140 $record = array(
2141 'course' => $course->id,
7ddb5f25
JL
2142 'name' => 'First quiz',
2143 'grade' => 90.00
13bb6819
JL
2144 );
2145 $options = array(
2146 'idnumber' => 'ABC',
2147 'visible' => 0
2148 );
2149 // Hidden activity.
7ddb5f25 2150 $quiz = self::getDataGenerator()->create_module('quiz', $record, $options);
13bb6819
JL
2151
2152 // Test admin user can see the complete hidden activity.
7ddb5f25 2153 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id);
13bb6819
JL
2154 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result);
2155
2156 $this->assertCount(0, $result['warnings']);
2157 // Test we retrieve all the fields.
7ddb5f25 2158 $this->assertCount(26, $result['cm']);
13bb6819 2159 $this->assertEquals($record['name'], $result['cm']['name']);
7ddb5f25 2160 $this->assertEquals($record['grade'], $result['cm']['grade']);
13bb6819
JL
2161 $this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
2162
2163 $student = $this->getDataGenerator()->create_user();
2164 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2165
2166 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
2167 $this->setUser($student);
2168
2169 // The user shouldn't be able to see the activity.
2170 try {
7ddb5f25 2171 core_course_external::get_course_module_by_instance('quiz', $quiz->id);
13bb6819
JL
2172 $this->fail('Exception expected due to invalid permissions.');
2173 } catch (moodle_exception $e) {
2174 $this->assertEquals('requireloginerror', $e->errorcode);
2175 }
2176
2177 // Make module visible.
7ddb5f25 2178 set_coursemodule_visible($quiz->cmid, 1);
13bb6819
JL
2179
2180 // Test student user.
7ddb5f25 2181 $result = core_course_external::get_course_module_by_instance('quiz', $quiz->id);
13bb6819
JL
2182 $result = external_api::clean_returnvalue(core_course_external::get_course_module_by_instance_returns(), $result);
2183
2184 $this->assertCount(0, $result['warnings']);
2185 // Test we retrieve only the few files we can see.
2186 $this->assertCount(11, $result['cm']);
7ddb5f25 2187 $this->assertEquals($quiz->cmid, $result['cm']['id']);
13bb6819 2188 $this->assertEquals($course->id, $result['cm']['course']);
7ddb5f25
JL
2189 $this->assertEquals('quiz', $result['cm']['modname']);
2190 $this->assertEquals($quiz->id, $result['cm']['instance']);
13bb6819
JL
2191
2192 // Try with an invalid module name.
2193 try {
7ddb5f25 2194 core_course_external::get_course_module_by_instance('abc', $quiz->id);
13bb6819
JL
2195 $this->fail('Exception expected due to invalid module name.');
2196 } catch (dml_read_exception $e) {
2197 $this->assertEquals('dmlreadexception', $e->errorcode);
2198 }
2199
2200 }
7c4e686f 2201
c115ff6a
JL
2202 /**
2203 * Test get_user_navigation_options
2204 */
2205 public function test_get_user_navigation_options() {
2206 global $USER;
2207
2208 $this->resetAfterTest();
2209 $course1 = self::getDataGenerator()->create_course();
2210 $course2 = self::getDataGenerator()->create_course();
2211
2212 // Create a viewer user.
2213 $viewer = self::getDataGenerator()->create_user();
2214 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id);
2215 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id);
2216
2217 $this->setUser($viewer->id);
2218 $courses = array($course1->id , $course2->id, SITEID);
2219
2220 $result = core_course_external::get_user_navigation_options($courses);
2221 $result = external_api::clean_returnvalue(core_course_external::get_user_navigation_options_returns(), $result);
2222
2223 $this->assertCount(0, $result['warnings']);
2224 $this->assertCount(3, $result['courses']);
2225
2226 foreach ($result['courses'] as $course) {
2227 $navoptions = new stdClass;
2228 foreach ($course['options'] as $option) {
2229 $navoptions->{$option['name']} = $option['available'];
2230 }
203f51d6 2231 $this->assertCount(9, $course['options']);
c115ff6a 2232 if ($course['id'] == SITEID) {
c115ff6a
JL
2233 $this->assertTrue($navoptions->blogs);
2234 $this->assertFalse($navoptions->notes);
2235 $this->assertFalse($navoptions->participants);
2236 $this->assertTrue($navoptions->badges);
2237 $this->assertTrue($navoptions->tags);
203f51d6 2238 $this->assertFalse($navoptions->grades);
c115ff6a
JL
2239 $this->assertFalse($navoptions->search);
2240 $this->assertTrue($navoptions->calendar);
99061152 2241 $this->assertTrue($navoptions->competencies);
c115ff6a 2242 } else {
c115ff6a
JL
2243 $this->assertTrue($navoptions->blogs);
2244 $this->assertFalse($navoptions->notes);
2245 $this->assertTrue($navoptions->participants);
2246 $this->assertTrue($navoptions->badges);
203f51d6 2247 $this->assertFalse($navoptions->tags);
99061152 2248 $this->assertTrue($navoptions->grades);
203f51d6
DP
2249 $this->assertFalse($navoptions->search);
2250 $this->assertFalse($navoptions->calendar);
99061152 2251 $this->assertTrue($navoptions->competencies);
c115ff6a
JL
2252 }
2253 }
2254 }
b9050b10
JL
2255
2256 /**
2257 * Test get_user_administration_options
2258 */
2259 public function test_get_user_administration_options() {
2260 global $USER;
2261
2262 $this->resetAfterTest();
2263 $course1 = self::getDataGenerator()->create_course();
2264 $course2 = self::getDataGenerator()->create_course();
2265
2266 // Create a viewer user.
2267 $viewer = self::getDataGenerator()->create_user();
2268 $this->getDataGenerator()->enrol_user($viewer->id, $course1->id);
2269 $this->getDataGenerator()->enrol_user($viewer->id, $course2->id);
2270
2271 $this->setUser($viewer->id);
2272 $courses = array($course1->id , $course2->id, SITEID);
2273
2274 $result = core_course_external::get_user_administration_options($courses);
2275 $result = external_api::clean_returnvalue(core_course_external::get_user_administration_options_returns(), $result);
2276
2277 $this->assertCount(0, $result['warnings']);
2278 $this->assertCount(3, $result['courses']);
2279
2280 foreach ($result['courses'] as $course) {
2281 $adminoptions = new stdClass;
2282 foreach ($course['options'] as $option) {
2283 $adminoptions->{$option['name']} = $option['available'];
2284 }
2285 if ($course['id'] == SITEID) {
0cbc248d 2286 $this->assertCount(16, $course['options']);
b9050b10
JL
2287 $this->assertFalse($adminoptions->update);
2288 $this->assertFalse($adminoptions->filters);
2289 $this->assertFalse($adminoptions->reports);
2290 $this->assertFalse($adminoptions->backup);
2291 $this->assertFalse($adminoptions->restore);
2292 $this->assertFalse($adminoptions->files);
c874d9aa
JL
2293 $this->assertFalse(!isset($adminoptions->tags));
2294 $this->assertFalse($adminoptions->gradebook);
2295 $this->assertFalse($adminoptions->outcomes);
2296 $this->assertFalse($adminoptions->badges);
2297 $this->assertFalse($adminoptions->import);
c874d9aa
JL
2298 $this->assertFalse($adminoptions->reset);
2299 $this->assertFalse($adminoptions->roles);
0cbc248d 2300 $this->assertFalse($adminoptions->editcompletion);
b9050b10 2301 } else {
020bad73 2302 $this->assertCount(14, $course['options']);
b9050b10
JL
2303 $this->assertFalse($adminoptions->update);
2304 $this->assertFalse($adminoptions->filters);
2305 $this->assertFalse($adminoptions->reports);
2306 $this->assertFalse($adminoptions->backup);
2307 $this->assertFalse($adminoptions->restore);
2308 $this->assertFalse($adminoptions->files);
2309 $this->assertFalse($adminoptions->tags);
2310 $this->assertFalse($adminoptions->gradebook);
2311 $this->assertFalse($adminoptions->outcomes);
2312 $this->assertTrue($adminoptions->badges);
2313 $this->assertFalse($adminoptions->import);
b9050b10
JL
2314 $this->assertFalse($adminoptions->reset);
2315 $this->assertFalse($adminoptions->roles);
0cbc248d 2316 $this->assertFalse($adminoptions->editcompletion);
b9050b10
JL
2317 }
2318 }
2319 }
80adabef
JL
2320
2321 /**
2322 * Test get_courses_by_fields
2323 */
2324 public function test_get_courses_by_field() {
2325 global $DB;
2326 $this->resetAfterTest(true);
2327
8c9a1964 2328 $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1'));
80adabef 2329 $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id));
cf58a2d5
JL
2330 $course1 = self::getDataGenerator()->create_course(
2331 array('category' => $category1->id, 'shortname' => 'c1', 'format' => 'topics'));
bfae6ca7
JL
2332
2333 $fieldcategory = self::getDataGenerator()->create_custom_field_category(['name' => 'Other fields']);
2334 $customfield = ['shortname' => 'test', 'name' => 'Custom field', 'type' => 'text',
2335 'categoryid' => $fieldcategory->get('id')];
2336 $field = self::getDataGenerator()->create_custom_field($customfield);
2337 $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value'];
2338 $course2 = self::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id, 'idnumber' => 'i2', 'customfields' => [$customfieldvalue]));
80adabef
JL
2339
2340 $student1 = self::getDataGenerator()->create_user();
2341 $user1 = self::getDataGenerator()->create_user();
2342 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2343 self::getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id);
2344 self::getDataGenerator()->enrol_user($student1->id, $course2->id, $studentrole->id);
2345
2346 self::setAdminUser();
2347 // As admins, we should be able to retrieve everything.
2348 $result = core_course_external::get_courses_by_field();
2349 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2350 $this->assertCount(3, $result['courses']);
2351 // Expect to receive all the fields.
bfae6ca7
JL
2352 $this->assertCount(38, $result['courses'][0]);
2353 $this->assertCount(39, $result['courses'][1]); // One more field because is not the site course.
2354 $this->assertCount(39, $result['courses'][2]); // One more field because is not the site course.
80adabef
JL
2355
2356 $result = core_course_external::get_courses_by_field('id', $course1->id);
2357 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2358 $this->assertCount(1, $result['courses']);
2359 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2360 // Expect to receive all the fields.
bfae6ca7 2361 $this->assertCount(39, $result['courses'][0]);
cf58a2d5
JL
2362 // Check default values for course format topics.
2363 $this->assertCount(2, $result['courses'][0]['courseformatoptions']);
2364 foreach ($result['courses'][0]['courseformatoptions'] as $option) {
2365 if ($option['name'] == 'hiddensections') {
2366 $this->assertEquals(0, $option['value']);
2367 } else {
2368 $this->assertEquals('coursedisplay', $option['name']);
2369 $this->assertEquals(0, $option['value']);
2370 }
2371 }
80adabef
JL
2372
2373 $result = core_course_external::get_courses_by_field('id', $course2->id);
2374 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2375 $this->assertCount(1, $result['courses']);
2376 $this->assertEquals($course2->id, $result['courses'][0]['id']);
bfae6ca7
JL
2377 // Check custom fields properly returned.
2378 unset($customfield['categoryid']);
2379 $this->assertEquals([array_merge($customfield, $customfieldvalue)], $result['courses'][0]['customfields']);
80adabef
JL
2380
2381 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id");
2382 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2383 $this->assertCount(2, $result['courses']);
2384
e45fc71e 2385 // Check default filters.
acae15c7
AA
2386 $this->assertCount(4, $result['courses'][0]['filters']);
2387 $this->assertCount(4, $result['courses'][1]['filters']);
e45fc71e 2388
80adabef
JL
2389 $result = core_course_external::get_courses_by_field('category', $category1->id);
2390 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2391 $this->assertCount(1, $result['courses']);
2392 $this->assertEquals($course1->id, $result['courses'][0]['id']);
8c9a1964 2393 $this->assertEquals('Cat 1', $result['courses'][0]['categoryname']);
80adabef
JL
2394
2395 $result = core_course_external::get_courses_by_field('shortname', 'c1');
2396 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2397 $this->assertCount(1, $result['courses']);
2398 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2399
2400 $result = core_course_external::get_courses_by_field('idnumber', 'i2');
2401 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2402 $this->assertCount(1, $result['courses']);
2403 $this->assertEquals($course2->id, $result['courses'][0]['id']);
2404
2405 $result = core_course_external::get_courses_by_field('idnumber', 'x');
2406 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2407 $this->assertCount(0, $result['courses']);
2408
e45fc71e
JL
2409 // Change filter value.
2410 filter_set_local_state('mediaplugin', context_course::instance($course1->id)->id, TEXTFILTER_OFF);
2411
80adabef
JL
2412 self::setUser($student1);
2413 // All visible courses (including front page) for normal student.
2414 $result = core_course_external::get_courses_by_field();
2415 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2416 $this->assertCount(2, $result['courses']);
bfae6ca7
JL
2417 $this->assertCount(31, $result['courses'][0]);
2418 $this->assertCount(32, $result['courses'][1]); // One field more (course format options), not present in site course.
80adabef
JL
2419
2420 $result = core_course_external::get_courses_by_field('id', $course1->id);
2421 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2422 $this->assertCount(1, $result['courses']);
2423 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2424 // Expect to receive all the files that a student can see.
bfae6ca7 2425 $this->assertCount(32, $result['courses'][0]);
e45fc71e
JL
2426
2427 // Check default filters.
2428 $filters = $result['courses'][0]['filters'];
acae15c7 2429 $this->assertCount(4, $filters);
e45fc71e
JL
2430 $found = false;
2431 foreach ($filters as $filter) {
2432 if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF) {
2433 $found = true;
2434 }
2435 }
2436 $this->assertTrue($found);
80adabef
JL
2437
2438 // Course 2 is not visible.
2439 $result = core_course_external::get_courses_by_field('id', $course2->id);
2440 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2441 $this->assertCount(0, $result['courses']);
2442
2443 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id");
2444 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2445 $this->assertCount(1, $result['courses']);
2446
2447 $result = core_course_external::get_courses_by_field('category', $category1->id);
2448 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2449 $this->assertCount(1, $result['courses']);
2450 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2451
2452 $result = core_course_external::get_courses_by_field('shortname', 'c1');
2453 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2454 $this->assertCount(1, $result['courses']);
2455 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2456
2457 $result = core_course_external::get_courses_by_field('idnumber', 'i2');
2458 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2459 $this->assertCount(0, $result['courses']);
2460
2461 $result = core_course_external::get_courses_by_field('idnumber', 'x');
2462 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2463 $this->assertCount(0, $result['courses']);
2464
2465 self::setUser($user1);
2466 // All visible courses (including front page) for authenticated user.
2467 $result = core_course_external::get_courses_by_field();
2468 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2469 $this->assertCount(2, $result['courses']);
bfae6ca7
JL
2470 $this->assertCount(31, $result['courses'][0]); // Site course.
2471 $this->assertCount(14, $result['courses'][1]); // Only public information, not enrolled.
80adabef
JL
2472
2473 $result = core_course_external::get_courses_by_field('id', $course1->id);
2474 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2475 $this->assertCount(1, $result['courses']);
2476 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2477 // Expect to receive all the files that a authenticated can see.
bfae6ca7 2478 $this->assertCount(14, $result['courses'][0]);
80adabef
JL
2479
2480 // Course 2 is not visible.
2481 $result = core_course_external::get_courses_by_field('id', $course2->id);
2482 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2483 $this->assertCount(0, $result['courses']);
2484
2485 $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id");
2486 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2487 $this->assertCount(1, $result['courses']);
2488
2489 $result = core_course_external::get_courses_by_field('category', $category1->id);
2490 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2491 $this->assertCount(1, $result['courses']);
2492 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2493
2494 $result = core_course_external::get_courses_by_field('shortname', 'c1');
2495 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2496 $this->assertCount(1, $result['courses']);
2497 $this->assertEquals($course1->id, $result['courses'][0]['id']);
2498
2499 $result = core_course_external::get_courses_by_field('idnumber', 'i2');
2500 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2501 $this->assertCount(0, $result['courses']);
2502
2503 $result = core_course_external::get_courses_by_field('idnumber', 'x');
2504 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2505 $this->assertCount(0, $result['courses']);
2506 }
2507
2508 public function test_get_courses_by_field_invalid_field() {
2509 $this->expectException('invalid_parameter_exception');
2510 $result = core_course_external::get_courses_by_field('zyx', 'x');
2511 }
2512
2513 public function test_get_courses_by_field_invalid_courses() {
2514 $result = core_course_external::get_courses_by_field('id', '-1');
2515 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2516 $this->assertCount(0, $result['courses']);
2517 }
26659f62 2518
6db24235
JL
2519 /**
2520 * Test get_courses_by_field_invalid_theme_and_lang
2521 */
2522 public function test_get_courses_by_field_invalid_theme_and_lang() {
2523 $this->resetAfterTest(true);
2524 $this->setAdminUser();
2525
2526 $course = self::getDataGenerator()->create_course(array('theme' => 'kkt', 'lang' => 'kkl'));
2527 $result = core_course_external::get_courses_by_field('id', $course->id);
2528 $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
2529 $this->assertEmpty($result['courses']['0']['theme']);
2530 $this->assertEmpty($result['courses']['0']['lang']);
2531 }
2532
2533
26659f62
JL
2534 public function test_check_updates() {
2535 global $DB;
2536 $this->resetAfterTest(true);
2537 $this->setAdminUser();
2538
2539 // Create different types of activities.
2540 $course = self::getDataGenerator()->create_course();
2541 $tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz',
2542 'resource', 'scorm', 'survey', 'url', 'wiki');
2543
2544 $modules = array();
2545 foreach ($tocreate as $modname) {
2546 $modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id));
2547 $modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid);
2548 $modules[$modname]['context'] = context_module::instance($modules[$modname]['instance']->cmid);
2549 }
2550
2551 $student = self::getDataGenerator()->create_user();
2552 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2553 self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
2554 $this->setUser($student);
2555
2556 $since = time();
2557 $this->waitForSecond();
2558 $params = array();
2559 foreach ($modules as $modname => $data) {
2560 $params[$data['cm']->id] = array(
2561 'contextlevel' => 'module',
2562 'id' => $data['cm']->id,
2563 'since' => $since
2564 );
2565 }
2566
2567 // Check there is nothing updated because modules are fresh new.
2568 $result = core_course_external::check_updates($course->id, $params);
2569 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
25adfbaa 2570 $this->assertCount(0, $result['instances']);
26659f62 2571 $this->assertCount(0, $result['warnings']);
26659f62 2572
879a8f56
JL
2573 // Test with get_updates_since the same data.
2574 $result = core_course_external::get_updates_since($course->id, $since);
2575 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result);
2576 $this->assertCount(0, $result['instances']);
2577 $this->assertCount(0, $result['warnings']);
2578
26659f62
JL
2579 // Update a module after a second.
2580 $this->waitForSecond();
2581 set_coursemodule_name($modules['forum']['cm']->id, 'New forum name');
2582
2583 $found = false;
2584 $result = core_course_external::check_updates($course->id, $params);
2585 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
25adfbaa 2586 $this->assertCount(1, $result['instances']);
26659f62
JL
2587 $this->assertCount(0, $result['warnings']);
2588 foreach ($result['instances'] as $module) {
2589 foreach ($module['updates'] as $update) {
2590 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') {
26659f62 2591 $found = true;
879a8f56
JL
2592 }
2593 }
2594 }
2595 $this->assertTrue($found);
2596
2597 // Test with get_updates_since the same data.
2598 $result = core_course_external::get_updates_since($course->id, $since);
2599 $result = external_api::clean_returnvalue(core_course_external::get_updates_since_returns(), $result);
2600 $this->assertCount(1, $result['instances']);
2601 $this->assertCount(0, $result['warnings']);
2602 $found = false;
2603 $this->assertCount(1, $result['instances']);
2604 $this->assertCount(0, $result['warnings']);
2605 foreach ($result['instances'] as $module) {
2606 foreach ($module['updates'] as $update) {
2607 if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') {
2608 $found = true;
26659f62
JL
2609 }
2610 }
2611 }
2612 $this->assertTrue($found);
2613
2614 // Do not retrieve the configuration field.
2615 $filter = array('files');
2616 $found = false;
2617 $result = core_course_external::check_updates($course->id, $params, $filter);
2618 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
25adfbaa 2619 $this->assertCount(0, $result['instances']);
26659f62 2620 $this->assertCount(0, $result['warnings']);
26659f62
JL
2621 $this->assertFalse($found);
2622
2623 // Add invalid cmid.
2624 $params[] = array(
2625 'contextlevel' => 'module',
2626 'id' => -2,
2627 'since' => $since
2628 );
2629 $result = core_course_external::check_updates($course->id, $params);
2630 $result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
2631 $this->assertCount(1, $result['warnings']);
2632 $this->assertEquals(-2, $result['warnings'][0]['itemid']);
2633 }
2c1d19fd
RW
2634
2635 /**
2636 * Test cases for the get_enrolled_courses_by_timeline_classification test.
2637 */
2638 public function get_get_enrolled_courses_by_timeline_classification_test_cases() {
2639 $now = time();
2640 $day = 86400;
2641
2642 $coursedata = [
2643 [
2644 'shortname' => 'apast',
2645 'startdate' => $now - ($day * 2),
2646 'enddate' => $now - $day
2647 ],
2648 [
2649 'shortname' => 'bpast',
2650 'startdate' => $now - ($day * 2),
2651 'enddate' => $now - $day
2652 ],
2653 [
2654 'shortname' => 'cpast',
2655 'startdate' => $now - ($day * 2),
2656 'enddate' => $now - $day
2657 ],
2658 [
2659 'shortname' => 'dpast',
2660 'startdate' => $now - ($day * 2),
2661 'enddate' => $now - $day
2662 ],
2663 [
2664 'shortname' => 'epast',
2665 'startdate' => $now - ($day * 2),
2666 'enddate' => $now - $day
2667 ],
2668 [
2669 'shortname' => 'ainprogress',
2670 'startdate' => $now - $day,
2671 'enddate' => $now + $day
2672 ],
2673 [
2674 'shortname' => 'binprogress',
2675 'startdate' => $now - $day,
2676 'enddate' => $now + $day
2677 ],
2678 [
2679 'shortname' => 'cinprogress',
2680 'startdate' => $now - $day,
2681 'enddate' => $now + $day
2682 ],
2683 [
2684 'shortname' => 'dinprogress',
2685 'startdate' => $now - $day,
2686 'enddate' => $now + $day
2687 ],
2688 [
2689 'shortname' => 'einprogress',
2690 'startdate' => $now - $day,
2691 'enddate' => $now + $day
2692 ],
2693 [
2694 'shortname' => 'afuture',
2695 'startdate' => $now + $day
2696 ],
2697 [
2698 'shortname' => 'bfuture',
2699 'startdate' => $now + $day
2700 ],
2701 [
2702 'shortname' => 'cfuture',
2703 'startdate' => $now + $day
2704 ],
2705 [
2706 'shortname' => 'dfuture',
2707 'startdate' => $now + $day
2708 ],
2709 [
2710 'shortname' => 'efuture',
2711 'startdate' => $now + $day
2712 ]
2713 ];
2714
2715 // Raw enrolled courses result set should be returned in this order:
2716 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
2717 // dfuture, dinprogress, dpast, efuture, einprogress, epast
2718 //
2719 // By classification the offset values for each record should be:
2720 // COURSE_TIMELINE_FUTURE
2721 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
2722 // COURSE_TIMELINE_INPROGRESS
2723 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
2724 // COURSE_TIMELINE_PAST
2725 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
2726 //
2727 // NOTE: The offset applies to the unfiltered full set of courses before the classification
2728 // filtering is done.
2729 // E.g. In our example if an offset of 2 is given then it would mean the first
2730 // two courses (afuture, ainprogress) are ignored.
2731 return [
2732 'empty set' => [
2733 'coursedata' => [],
2734 'classification' => 'future',
2735 'limit' => 2,
2736 'offset' => 0,
2737 'expectedcourses' => [],
2738 'expectednextoffset' => 0
2739 ],
2740 // COURSE_TIMELINE_FUTURE.
2741 'future not limit no offset' => [
2742 'coursedata' => $coursedata,
2743 'classification' => 'future',
2744 'limit' => 0,
2745 'offset' => 0,
2746 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2747 'expectednextoffset' => 15
2748 ],
2749 'future no offset' => [
2750 'coursedata' => $coursedata,
2751 'classification' => 'future',
2752 'limit' => 2,
2753 'offset' => 0,
2754 'expectedcourses' => ['afuture', 'bfuture'],
2755 'expectednextoffset' => 4
2756 ],
2757 'future offset' => [
2758 'coursedata' => $coursedata,
2759 'classification' => 'future',
2760 'limit' => 2,
2761 'offset' => 2,
2762 'expectedcourses' => ['bfuture', 'cfuture'],
2763 'expectednextoffset' => 7
2764 ],
2765 'future exact limit' => [
2766 'coursedata' => $coursedata,
2767 'classification' => 'future',
2768 'limit' => 5,
2769 'offset' => 0,
2770 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2771 'expectednextoffset' => 13
2772 ],
2773 'future limit less results' => [
2774 'coursedata' => $coursedata,
2775 'classification' => 'future',
2776 'limit' => 10,
2777 'offset' => 0,
2778 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
2779 'expectednextoffset' => 15
2780 ],
2781 'future limit less results with offset' => [
2782 'coursedata' => $coursedata,
2783 'classification' => 'future',
2784 'limit' => 10,
2785 'offset' => 5,
2786 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
2787 'expectednextoffset' => 15
2788 ],
6481a21f
BB
2789 'all no limit or offset' => [
2790 'coursedata' => $coursedata,
2791 'classification' => 'all',
2792 'limit' => 0,
2793 'offset' => 0,
2794 'expectedcourses' => [
2795 'afuture',
2796 'ainprogress',
2797 'apast',
2798 'bfuture',
2799 'binprogress',
2800 'bpast',
2801 'cfuture',
2802 'cinprogress',
2803 'cpast',
2804 'dfuture',
2805 'dinprogress',
2806 'dpast',
2807 'efuture',
2808 'einprogress',
2809 'epast'
2810 ],
2811 'expectednextoffset' => 15
2812 ],
2813 'all limit no offset' => [
2814 'coursedata' => $coursedata,
2815 'classification' => 'all',
2816 'limit' => 5,
2817 'offset' => 0,
2818 'expectedcourses' => [
2819 'afuture',
2820 'ainprogress',
2821 'apast',
2822 'bfuture',
2823 'binprogress'
2824 ],
2825 'expectednextoffset' => 5
2826 ],
2827 'all limit and offset' => [
2828 'coursedata' => $coursedata,
2829 'classification' => 'all',
2830 'limit' => 5,
2831 'offset' => 5,
2832 'expectedcourses' => [
2833 'bpast',
2834 'cfuture',
2835 'cinprogress',
2836 'cpast',
2837 'dfuture'
2838 ],
2839 'expectednextoffset' => 10
2840 ],
2841 'all offset past result set' => [
2842 'coursedata' => $coursedata,
2843 'classification' => 'all',
2844 'limit' => 5,
2845 'offset' => 50,
2846 'expectedcourses' => [],
2847 'expectednextoffset' => 50
2848 ],
2c1d19fd
RW
2849 ];
2850 }
2851
2852 /**
2853 * Test the get_enrolled_courses_by_timeline_classification function.
2854 *
2855 * @dataProvider get_get_enrolled_courses_by_timeline_classification_test_cases()
2856 * @param array $coursedata Courses to create
2857 * @param string $classification Timeline classification
2858 * @param int $limit Maximum number of results
2859 * @param int $offset Offset the unfiltered courses result set by this amount
2860 * @param array $expectedcourses Expected courses in result
2861 * @param int $expectednextoffset Expected next offset value in result
2862 */
2863 public function test_get_enrolled_courses_by_timeline_classification(
2864 $coursedata,
2865 $classification,
2866 $limit,
2867 $offset,
2868 $expectedcourses,
2869 $expectednextoffset
2870 ) {
2871 $this->resetAfterTest();
2872 $generator = $this->getDataGenerator();
2873
2874 $courses = array_map(function($coursedata) use ($generator) {
2875 return $generator->create_course($coursedata);
2876 }, $coursedata);
2877
2878 $student = $generator->create_user();
2879
2880 foreach ($courses as $course) {
2881 $generator->enrol_user($student->id, $course->id, 'student');
2882 }
2883
2884 $this->setUser($student);
2885
2886 // NOTE: The offset applies to the unfiltered full set of courses before the classification
2887 // filtering is done.
2888 // E.g. In our example if an offset of 2 is given then it would mean the first
2889 // two courses (afuture, ainprogress) are ignored.
2890 $result = core_course_external::get_enrolled_courses_by_timeline_classification(
2891 $classification,
2892 $limit,
2893 $offset,
2894 'shortname ASC'
2895 );
2896 $result = external_api::clean_returnvalue(
2897 core_course_external::get_enrolled_courses_by_timeline_classification_returns(),
2898 $result
2899 );
2900
2901 $actual = array_map(function($course) {
2902 return $course['shortname'];
2903 }, $result['courses']);
2904
2905 $this->assertEquals($expectedcourses, $actual);
2906 $this->assertEquals($expectednextoffset, $result['nextoffset']);
2907 }
98a52c80
VD
2908
2909 /**
2910 * Test the get_recent_courses function.
2911 */
2912 public function test_get_recent_courses() {
2913 global $USER, $DB;
2914
2915 $this->resetAfterTest();
2916 $generator = $this->getDataGenerator();
2917
2918 set_config('hiddenuserfields', 'lastaccess');
2919
2920 $courses = array();
2921 for ($i = 1; $i < 12; $i++) {
2922 $courses[] = $generator->create_course();
2923 };
2924
2925 $student = $generator->create_user();
2926 $teacher = $generator->create_user();
2927
2928 foreach ($courses as $course) {
2929 $generator->enrol_user($student->id, $course->id, 'student');
2930 }
2931
2932 $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher');
2933
2934 $this->setUser($student);
2935
2936 $result = core_course_external::get_recent_courses($USER->id);
2937
2938 // No course accessed.
2939 $this->assertCount(0, $result);
2940
2941 foreach ($courses as $course) {
2942 core_course_external::view_course($course->id);
2943 }
2944
2945 // Every course accessed.
2946 $result = core_course_external::get_recent_courses($USER->id);
2947 $this->assertCount( 11, $result);
2948
2949 // Every course accessed, result limited to 10 courses.
2950 $result = core_course_external::get_recent_courses($USER->id, 10);
2951 $this->assertCount(10, $result);
2952
2953 $guestcourse = $generator->create_course(
2954 (object)array('shortname' => 'guestcourse',
2955 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
2956 'enrol_guest_password_0' => ''));
2957 core_course_external::view_course($guestcourse->id);
2958
2959 // Every course accessed, even the not enrolled one.
2960 $result = core_course_external::get_recent_courses($USER->id);
2961 $this->assertCount(12, $result);
2962
2963 // Offset 5, return 7 out of 12.
2964 $result = core_course_external::get_recent_courses($USER->id, 0, 5);
2965 $this->assertCount(7, $result);
2966
2967 // Offset 5 and limit 3, return 3 out of 12.
2968 $result = core_course_external::get_recent_courses($USER->id, 3, 5);
2969 $this->assertCount(3, $result);
2970
2971 // Sorted by course id ASC.
2972 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id ASC');
2973 $this->assertEquals($courses[0]->id, array_shift($result)->id);
2974
2975 // Sorted by course id DESC.
2976 $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id DESC');
2977 $this->assertEquals($guestcourse->id, array_shift($result)->id);
2978
2979 // If last access is hidden, only get the courses where has viewhiddenuserfields capability.
2980 $this->setUser($teacher);
2981 $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
2982 $usercontext = context_user::instance($student->id);
2983 $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid);
2984
2985 // Sorted by course id DESC.
2986 $result = core_course_external::get_recent_courses($student->id);
2987 $this->assertCount(1, $result);
2988 $this->assertEquals($courses[0]->id, array_shift($result)->id);
2989 }
06e50afd
MM
2990
2991 /**
2992 * Test get enrolled users by cmid function.
2993 */
2994 public function test_get_enrolled_users_by_cmid() {
9d752481 2995 global $PAGE;
06e50afd
MM
2996 $this->resetAfterTest(true);
2997
2998 $user1 = self::getDataGenerator()->create_user();
2999 $user2 = self::getDataGenerator()->create_user();
3000
9d752481
MM
3001 $user1picture = new user_picture($user1);
3002 $user1picture->size = 1;
3003 $user1->profileimage = $user1picture->get_url($PAGE)->out(false);
3004
3005 $user2picture = new user_picture($user2);
3006 $user2picture->size = 1;
3007 $user2->profileimage = $user2picture->get_url($PAGE)->out(false);
3008
06e50afd
MM
3009 // Set the first created user to the test user.
3010 self::setUser($user1);
3011
3012 // Create course to add the module.
3013 $course1 = self::getDataGenerator()->create_course();
3014
3015 // Forum with tracking off.
3016 $record = new stdClass();
3017 $record->course = $course1->id;
3018 $forum1 = self::getDataGenerator()->create_module('forum', $record);
3019
3020 // Following lines enrol and assign default role id to the users.
3021 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
3022 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
3023
3024 // Create what we expect to be returned when querying the course module.
3025 $expectedusers = array(
3026 'users' => array(),
3027 'warnings' => array(),
3028 );
3029
3030 $expectedusers['users'][0] = [
3031 'id' => $user1->id,
3032 'fullname' => fullname($user1),
3033 'firstname' => $user1->firstname,
3034 'lastname' => $user1->lastname,
9d752481 3035 'profileimage' => $user1->profileimage,
06e50afd
MM
3036 ];
3037 $expectedusers['users'][1] = [
3038 'id' => $user2->id,
3039 'fullname' => fullname($user2),
3040 'firstname' => $user2->firstname,
3041 'lastname' => $user2->lastname,
9d752481 3042 'profileimage' => $user2->profileimage,
06e50afd
MM
3043 ];
3044
3045 // Test getting the users in a given context.
3046 $users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid);
3047 $users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users);
3048
3049 $this->assertEquals(2, count($users['users']));
3050 $this->assertEquals($expectedusers, $users);
3051 }
05b27f21
MM
3052
3053 /**
3054 * Test fetch_modules_activity_chooser
3055 */
3056 public function test_fetch_modules_activity_chooser() {
3057 global $OUTPUT;
3058
3059 $this->resetAfterTest(true);
3060
3061 // Log in as Admin.
3062 $this->setAdminUser();
3063
3064 $course1 = self::getDataGenerator()->create_course();
3065
3066 // Fetch course modules.
3067 $result = core_course_external::fetch_modules_activity_chooser($course1->id);
3068 $result = external_api::clean_returnvalue(core_course_external::fetch_modules_activity_chooser_returns(), $result);
3069 // Check for 0 warnings.
3070 $this->assertEquals(0, count($result['warnings']));
3071 // Check we have the right number of standard modules.
3072 $this->assertEquals(21, count($result['allmodules']));
3073
3074 $coursecontext = context_course::instance($course1->id);
3075 $modnames = get_module_types_names();
3076 $modules = get_module_metadata($course1, $modnames, null);
3077 $related = [
3078 'context' => $coursecontext
3079 ];
3080 // Export the module chooser data.
3081 $modchooserdata = new \core_course\external\course_module_chooser_exporter($modules, $related);
3082 $formatteddata = $modchooserdata->export($OUTPUT)->options;
3083
3084 // Check if the webservice returns exactly what the exporter defines.
3085 $this->assertEquals($formatteddata, $result['allmodules']);
3086 }
76724712 3087}