weekly release 2.9dev
[moodle.git] / course / tests / courselib_test.php
CommitLineData
354b214c
PS
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 * Course related unit tests
19 *
20 * @package core
21 * @category phpunit
22 * @copyright 2012 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
f70bfb84 28global $CFG;
7cbb5070
MN
29require_once($CFG->dirroot . '/course/lib.php');
30require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
31require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
cc033d48 32require_once($CFG->dirroot . '/tag/lib.php');
354b214c 33
8252b7c2 34class core_course_courselib_testcase extends advanced_testcase {
354b214c 35
c42d1818
AG
36 /**
37 * Tidy up open files that may be left open.
38 */
39 protected function tearDown() {
40 gc_collect_cycles();
41 }
42
dd5d933f 43 /**
7bf4f6e9
JM
44 * Set forum specific test values for calling create_module().
45 *
46 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
dd5d933f 47 */
7bf4f6e9
JM
48 private function forum_create_set_values(&$moduleinfo) {
49 // Completion specific to forum - optional.
50 $moduleinfo->completionposts = 3;
51 $moduleinfo->completiondiscussions = 1;
52 $moduleinfo->completionreplies = 2;
dd5d933f 53
7cb0ea2c 54 // Specific values to the Forum module.
dd5d933f
JM
55 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
56 $moduleinfo->type = 'single';
bd8f5d45 57 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
7bf4f6e9
JM
58 $moduleinfo->maxbytes = 10240;
59 $moduleinfo->maxattachments = 2;
60
7cb0ea2c 61 // Post threshold for blocking - specific to forum.
7bf4f6e9
JM
62 $moduleinfo->blockperiod = 60*60*24;
63 $moduleinfo->blockafter = 10;
64 $moduleinfo->warnafter = 5;
65 }
dd5d933f 66
7bf4f6e9
JM
67 /**
68 * Execute test asserts on the saved DB data by create_module($forum).
69 *
70 * @param object $moduleinfo - the specific forum values that were used to create a forum.
71 * @param object $dbmodinstance - the DB values of the created forum.
72 */
73 private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
74 // Compare values specific to forums.
75 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
76 $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
77 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
78 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
79 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
80 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
81 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
82 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
83 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
84 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
85 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
86 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
87 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
88 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
89 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
90 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
91 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
92 }
dd5d933f 93
7bf4f6e9
JM
94 /**
95 * Set assign module specific test values for calling create_module().
96 *
97 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
98 */
99 private function assign_create_set_values(&$moduleinfo) {
7cb0ea2c 100 // Specific values to the Assign module.
dd5d933f
JM
101 $moduleinfo->alwaysshowdescription = true;
102 $moduleinfo->submissiondrafts = true;
103 $moduleinfo->requiresubmissionstatement = true;
104 $moduleinfo->sendnotifications = true;
105 $moduleinfo->sendlatenotifications = true;
7bf4f6e9
JM
106 $moduleinfo->duedate = time() + (7 * 24 * 3600);
107 $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
108 $moduleinfo->allowsubmissionsfromdate = time();
dd5d933f
JM
109 $moduleinfo->teamsubmission = true;
110 $moduleinfo->requireallteammemberssubmit = true;
111 $moduleinfo->teamsubmissiongroupingid = true;
112 $moduleinfo->blindmarking = true;
7f198356
DW
113 $moduleinfo->markingworkflow = true;
114 $moduleinfo->markingallocation = true;
dd5d933f
JM
115 $moduleinfo->assignsubmission_onlinetext_enabled = true;
116 $moduleinfo->assignsubmission_file_enabled = true;
117 $moduleinfo->assignsubmission_file_maxfiles = 1;
118 $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
119 $moduleinfo->assignsubmission_comments_enabled = true;
120 $moduleinfo->assignfeedback_comments_enabled = true;
121 $moduleinfo->assignfeedback_offline_enabled = true;
122 $moduleinfo->assignfeedback_file_enabled = true;
123
7cb0ea2c 124 // Advanced grading.
7bf4f6e9
JM
125 $gradingmethods = grading_manager::available_methods();
126 $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
127 }
dd5d933f 128
7bf4f6e9
JM
129 /**
130 * Execute test asserts on the saved DB data by create_module($assign).
131 *
132 * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
133 * @param object $dbmodinstance - the DB values of the created assign module.
134 */
135 private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
136 global $DB;
137
138 $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
139 $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
140 $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
141 $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
142 $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
143 $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
144 $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
145 $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
146 $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
147 $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
148 $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
7f198356
DW
149 $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
150 $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
7bf4f6e9
JM
151 // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
152
7cb0ea2c 153 // Advanced grading.
6a20bfa0
DW
154 $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
155 $contextmodule = context_module::instance($cm->id);
7bf4f6e9
JM
156 $advancedgradingmethod = $DB->get_record('grading_areas',
157 array('contextid' => $contextmodule->id,
158 'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
159 $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
160 }
161
162 /**
163 * Run some asserts test for a specific module for the function create_module().
164 *
165 * The function has been created (and is called) for $this->test_create_module().
166 * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
167 * So if you want, you can overwrite the default values/asserts in the respective functions.
168 * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
169 */
170 private function create_specific_module_test($modulename) {
171 global $DB, $CFG;
172
173 $this->resetAfterTest(true);
174
175 $this->setAdminUser();
176
177 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
178 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
179
180 // Enable avaibility.
181 // If not enabled all conditional fields will be ignored.
182 set_config('enableavailability', 1);
183
184 // Enable course completion.
185 // If not enabled all completion settings will be ignored.
186 set_config('enablecompletion', COMPLETION_ENABLED);
187
188 // Enable forum RSS feeds.
189 set_config('enablerssfeeds', 1);
190 set_config('forum_enablerssfeeds', 1);
191
192 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
193 array('createsections'=>true));
194
195 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
dd5d933f 196
7cb0ea2c 197 // Create assign module instance for test.
7bf4f6e9
JM
198 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
199 $params['course'] = $course->id;
200 $instance = $generator->create_instance($params);
201 $assigncm = get_coursemodule_from_instance('assign', $instance->id);
dd5d933f 202
7bf4f6e9
JM
203 // Module test values.
204 $moduleinfo = new stdClass();
dd5d933f 205
7bf4f6e9
JM
206 // Always mandatory generic values to any module.
207 $moduleinfo->modulename = $modulename;
208 $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
209 $moduleinfo->course = $course->id;
210 $moduleinfo->groupingid = $grouping->id;
7bf4f6e9
JM
211 $moduleinfo->visible = true;
212
213 // Sometimes optional generic values for some modules.
214 $moduleinfo->name = 'My test module';
215 $moduleinfo->showdescription = 1; // standard boolean
216 require_once($CFG->libdir . '/gradelib.php');
217 $gradecats = grade_get_categories_menu($moduleinfo->course, false);
218 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
219 $moduleinfo->gradecat = $gradecatid;
220 $moduleinfo->groupmode = VISIBLEGROUPS;
221 $moduleinfo->cmidnumber = 'idnumber_XXX';
222
223 // Completion common to all module.
224 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
225 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
7cb0ea2c 226 $moduleinfo->completiongradeitemnumber = 1;
7bf4f6e9
JM
227 $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
228
229 // Conditional activity.
8d1f33e1 230 $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
231 '{"type":"date","d":">=","t":' . time() . '},' .
232 '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
233 ']}';
7bf4f6e9
JM
234 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
235 $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
e56e8e3a 236 $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
7bf4f6e9
JM
237 $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
238
239 // Grading and Advanced grading.
240 require_once($CFG->dirroot . '/rating/lib.php');
241 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
242 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
243 $moduleinfo->assesstimestart = time();
244 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
245
7cb0ea2c 246 // RSS.
7bf4f6e9
JM
247 $moduleinfo->rsstype = 2;
248 $moduleinfo->rssarticles = 10;
249
250 // Optional intro editor (depends of module).
7bf4f6e9
JM
251 $draftid_editor = 0;
252 file_prepare_draft_area($draftid_editor, null, null, null, null);
253 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
dd5d933f 254
7bf4f6e9
JM
255 // Following is the advanced grading method area called 'submissions' for the 'assign' module.
256 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
257 $moduleinfo->grade = 100;
258 }
7cb0ea2c 259
7bf4f6e9
JM
260 // Plagiarism form values.
261 // No plagiarism plugin installed by default. Use this space to make your own test.
262
263 // Values specific to the module.
264 $modulesetvalues = $modulename.'_create_set_values';
265 $this->$modulesetvalues($moduleinfo);
266
267 // Create the module.
268 $result = create_module($moduleinfo);
269
270 // Retrieve the module info.
271 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
272 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
273 // We passed the course section number to create_courses but $dbcm contain the section id.
274 // We need to retrieve the db course section number.
275 $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
7cb0ea2c 276 // Retrieve the grade item.
7bf4f6e9
JM
277 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
278 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
279
280 // Compare the values common to all module instances.
281 $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
282 $this->assertEquals($moduleinfo->section, $section->section);
283 $this->assertEquals($moduleinfo->course, $dbcm->course);
284 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
7bf4f6e9
JM
285 $this->assertEquals($moduleinfo->visible, $dbcm->visible);
286 $this->assertEquals($moduleinfo->completion, $dbcm->completion);
287 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
288 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
289 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
8d1f33e1 290 $this->assertEquals($moduleinfo->availability, $dbcm->availability);
7bf4f6e9
JM
291 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
292 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
293 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
294 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
295
296 // Optional grade testing.
297 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
298 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
299 }
300
301 // Some optional (but quite common) to some module.
302 $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
303 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
304 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
305
7cb0ea2c 306 // Test specific to the module.
7bf4f6e9
JM
307 $modulerunasserts = $modulename.'_create_run_asserts';
308 $this->$modulerunasserts($moduleinfo, $dbmodinstance);
b9b994c7 309 return $moduleinfo;
7bf4f6e9 310 }
dd5d933f 311
7bf4f6e9
JM
312 /**
313 * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
314 */
315 public function test_create_module() {
316 // Add the module name you want to test here.
7cb0ea2c 317 // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
7bf4f6e9
JM
318 $modules = array('forum', 'assign');
319 // Run all tests.
320 foreach ($modules as $modulename) {
321 $this->create_specific_module_test($modulename);
322 }
dd5d933f
JM
323 }
324
325 /**
7bf4f6e9 326 * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
dd5d933f
JM
327 */
328 public function test_update_module() {
7bf4f6e9 329 // Add the module name you want to test here.
7cb0ea2c 330 // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
7bf4f6e9
JM
331 $modules = array('forum');
332 // Run all tests.
333 foreach ($modules as $modulename) {
334 $this->update_specific_module_test($modulename);
335 }
336 }
337
338 /**
339 * Set forum specific test values for calling update_module().
340 *
341 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
342 */
343 private function forum_update_set_values(&$moduleinfo) {
344 // Completion specific to forum - optional.
345 $moduleinfo->completionposts = 3;
346 $moduleinfo->completiondiscussions = 1;
347 $moduleinfo->completionreplies = 2;
348
7cb0ea2c 349 // Specific values to the Forum module.
7bf4f6e9
JM
350 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
351 $moduleinfo->type = 'single';
bd8f5d45 352 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
7bf4f6e9
JM
353 $moduleinfo->maxbytes = 10240;
354 $moduleinfo->maxattachments = 2;
355
7cb0ea2c 356 // Post threshold for blocking - specific to forum.
7bf4f6e9
JM
357 $moduleinfo->blockperiod = 60*60*24;
358 $moduleinfo->blockafter = 10;
359 $moduleinfo->warnafter = 5;
360 }
361
362 /**
363 * Execute test asserts on the saved DB data by update_module($forum).
364 *
365 * @param object $moduleinfo - the specific forum values that were used to update a forum.
366 * @param object $dbmodinstance - the DB values of the updated forum.
367 */
368 private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
369 // Compare values specific to forums.
370 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
371 $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
372 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
373 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
374 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
375 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
376 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
377 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
378 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
379 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
380 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
381 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
382 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
383 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
384 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
385 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
386 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
387 }
388
389
390
391 /**
392 * Test a specific type of module.
393 *
394 * @param string $modulename - the module name to test
395 */
396 private function update_specific_module_test($modulename) {
dd5d933f
JM
397 global $DB, $CFG;
398
399 $this->resetAfterTest(true);
400
401 $this->setAdminUser();
402
7bf4f6e9
JM
403 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
404 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
dd5d933f 405
7bf4f6e9
JM
406 // Enable avaibility.
407 // If not enabled all conditional fields will be ignored.
408 set_config('enableavailability', 1);
409
410 // Enable course completion.
411 // If not enabled all completion settings will be ignored.
412 set_config('enablecompletion', COMPLETION_ENABLED);
413
414 // Enable forum RSS feeds.
415 set_config('enablerssfeeds', 1);
416 set_config('forum_enablerssfeeds', 1);
417
418 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
dd5d933f
JM
419 array('createsections'=>true));
420
421 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
422
7cb0ea2c 423 // Create assign module instance for testing gradeitem.
7bf4f6e9
JM
424 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
425 $params['course'] = $course->id;
426 $instance = $generator->create_instance($params);
427 $assigncm = get_coursemodule_from_instance('assign', $instance->id);
dd5d933f 428
7cb0ea2c 429 // Create the test forum to update.
7bf4f6e9
JM
430 $initvalues = new stdClass();
431 $initvalues->introformat = FORMAT_HTML;
432 $initvalues->course = $course->id;
433 $forum = self::getDataGenerator()->create_module('forum', $initvalues);
dd5d933f 434
7cb0ea2c 435 // Retrieve course module.
7bf4f6e9 436 $cm = get_coursemodule_from_instance('forum', $forum->id);
dd5d933f 437
7bf4f6e9 438 // Module test values.
dd5d933f
JM
439 $moduleinfo = new stdClass();
440
7bf4f6e9 441 // Always mandatory generic values to any module.
dd5d933f 442 $moduleinfo->coursemodule = $cm->id;
7bf4f6e9
JM
443 $moduleinfo->modulename = $modulename;
444 $moduleinfo->course = $course->id;
445 $moduleinfo->groupingid = $grouping->id;
7bf4f6e9
JM
446 $moduleinfo->visible = true;
447
448 // Sometimes optional generic values for some modules.
449 $moduleinfo->name = 'My test module';
450 $moduleinfo->showdescription = 1; // standard boolean
451 require_once($CFG->libdir . '/gradelib.php');
452 $gradecats = grade_get_categories_menu($moduleinfo->course, false);
453 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
454 $moduleinfo->gradecat = $gradecatid;
455 $moduleinfo->groupmode = VISIBLEGROUPS;
456 $moduleinfo->cmidnumber = 'idnumber_XXX';
457
458 // Completion common to all module.
459 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
460 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
7cb0ea2c 461 $moduleinfo->completiongradeitemnumber = 1;
7bf4f6e9 462 $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
69a0e65c 463 $moduleinfo->completionunlocked = 1;
7bf4f6e9
JM
464
465 // Conditional activity.
7bf4f6e9 466 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
06c06038 467 $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
468 array(\availability_date\condition::get_json('>=', time()),
469 \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
470 \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
471 \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
472 \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
7bf4f6e9
JM
473
474 // Grading and Advanced grading.
475 require_once($CFG->dirroot . '/rating/lib.php');
476 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
477 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
478 $moduleinfo->assesstimestart = time();
479 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
480
7cb0ea2c 481 // RSS.
7bf4f6e9
JM
482 $moduleinfo->rsstype = 2;
483 $moduleinfo->rssarticles = 10;
484
485 // Optional intro editor (depends of module).
dd5d933f
JM
486 $draftid_editor = 0;
487 file_prepare_draft_area($draftid_editor, null, null, null, null);
7bf4f6e9 488 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
dd5d933f 489
7bf4f6e9
JM
490 // Following is the advanced grading method area called 'submissions' for the 'assign' module.
491 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
492 $moduleinfo->grade = 100;
493 }
494 // Plagiarism form values.
dd5d933f
JM
495 // No plagiarism plugin installed by default. Use this space to make your own test.
496
7bf4f6e9
JM
497 // Values specific to the module.
498 $modulesetvalues = $modulename.'_update_set_values';
499 $this->$modulesetvalues($moduleinfo);
500
501 // Create the module.
502 $result = update_module($moduleinfo);
503
504 // Retrieve the module info.
505 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
506 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
7cb0ea2c 507 // Retrieve the grade item.
7bf4f6e9
JM
508 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
509 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
510
511 // Compare the values common to all module instances.
512 $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
513 $this->assertEquals($moduleinfo->course, $dbcm->course);
514 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
7bf4f6e9
JM
515 $this->assertEquals($moduleinfo->visible, $dbcm->visible);
516 $this->assertEquals($moduleinfo->completion, $dbcm->completion);
517 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
518 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
519 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
8d1f33e1 520 $this->assertEquals($moduleinfo->availability, $dbcm->availability);
7bf4f6e9
JM
521 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
522 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
523 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
524 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
525
526 // Optional grade testing.
527 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
528 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
529 }
530
531 // Some optional (but quite common) to some module.
532 $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
533 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
534 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
535
7cb0ea2c 536 // Test specific to the module.
7bf4f6e9
JM
537 $modulerunasserts = $modulename.'_update_run_asserts';
538 $this->$modulerunasserts($moduleinfo, $dbmodinstance);
b9b994c7 539 return $moduleinfo;
7bf4f6e9 540 }
dd5d933f
JM
541
542
f70bfb84
FM
543 public function test_create_course() {
544 global $DB;
545 $this->resetAfterTest(true);
546 $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
547
548 $course = new stdClass();
549 $course->fullname = 'Apu loves Unit Təsts';
550 $course->shortname = 'Spread the lŭve';
551 $course->idnumber = '123';
552 $course->summary = 'Awesome!';
553 $course->summaryformat = FORMAT_PLAIN;
554 $course->format = 'topics';
555 $course->newsitems = 0;
556 $course->numsections = 5;
557 $course->category = $defaultcategory;
97d5e39c 558 $original = (array) $course;
f70bfb84
FM
559
560 $created = create_course($course);
561 $context = context_course::instance($created->id);
562
563 // Compare original and created.
f70bfb84
FM
564 $this->assertEquals($original, array_intersect_key((array) $created, $original));
565
566 // Ensure default section is created.
567 $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
568 $this->assertTrue($sectioncreated);
569
570 // Ensure blocks have been associated to the course.
571 $blockcount = $DB->count_records('block_instances', array('parentcontextid' => $context->id));
572 $this->assertGreaterThan(0, $blockcount);
9930e426
CF
573
574 // Ensure that the shortname isn't duplicated.
575 try {
576 $created = create_course($course);
2793260f 577 $this->fail('Exception expected');
9930e426 578 } catch (moodle_exception $e) {
2793260f 579 $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
9930e426 580 }
9930e426
CF
581
582 // Ensure that the idnumber isn't duplicated.
583 $course->shortname .= '1';
584 try {
585 $created = create_course($course);
2793260f 586 $this->fail('Exception expected');
9930e426 587 } catch (moodle_exception $e) {
2793260f 588 $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
9930e426 589 }
f70bfb84
FM
590 }
591
384c3510
MG
592 public function test_create_course_with_generator() {
593 global $DB;
594 $this->resetAfterTest(true);
595 $course = $this->getDataGenerator()->create_course();
596
597 // Ensure default section is created.
598 $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
599 $this->assertTrue($sectioncreated);
600 }
601
602 public function test_create_course_sections() {
603 global $DB;
604 $this->resetAfterTest(true);
605
606 $course = $this->getDataGenerator()->create_course(
607 array('shortname' => 'GrowingCourse',
608 'fullname' => 'Growing Course',
609 'numsections' => 5),
610 array('createsections' => true));
611
4a3fb71c 612 // Ensure all 6 (0-5) sections were created and course content cache works properly
384c3510
MG
613 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
614 $this->assertEquals(range(0, $course->numsections), $sectionscreated);
615
616 // this will do nothing, section already exists
617 $this->assertFalse(course_create_sections_if_missing($course, $course->numsections));
618
619 // this will create new section
620 $this->assertTrue(course_create_sections_if_missing($course, $course->numsections + 1));
621
622 // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
623 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
624 $this->assertEquals(range(0, $course->numsections + 1), $sectionscreated);
625 }
626
5536a561
FD
627 public function test_update_course() {
628 global $DB;
629
630 $this->resetAfterTest();
c3bf6181
MN
631
632 $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
5536a561
FD
633
634 $course = new stdClass();
635 $course->fullname = 'Apu loves Unit Təsts';
636 $course->shortname = 'test1';
637 $course->idnumber = '1';
638 $course->summary = 'Awesome!';
639 $course->summaryformat = FORMAT_PLAIN;
640 $course->format = 'topics';
641 $course->newsitems = 0;
642 $course->numsections = 5;
643 $course->category = $defaultcategory;
5536a561
FD
644
645 $created = create_course($course);
646 // Ensure the checks only work on idnumber/shortname that are not already ours.
c3bf6181 647 update_course($created);
5536a561
FD
648
649 $course->shortname = 'test2';
650 $course->idnumber = '2';
651
652 $created2 = create_course($course);
653
654 // Test duplicate idnumber.
655 $created2->idnumber = '1';
656 try {
657 update_course($created2);
658 $this->fail('Expected exception when trying to update a course with duplicate idnumber');
659 } catch (moodle_exception $e) {
660 $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
661 }
662
663 // Test duplicate shortname.
664 $created2->idnumber = '2';
665 $created2->shortname = 'test1';
5536a561
FD
666 try {
667 update_course($created2);
668 $this->fail('Expected exception when trying to update a course with a duplicate shortname');
669 } catch (moodle_exception $e) {
670 $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
671 }
672 }
673
5523c344 674 public function test_course_add_cm_to_section() {
675 global $DB;
676 $this->resetAfterTest(true);
677
678 // Create course with 1 section.
679 $course = $this->getDataGenerator()->create_course(
680 array('shortname' => 'GrowingCourse',
681 'fullname' => 'Growing Course',
682 'numsections' => 1),
683 array('createsections' => true));
684
685 // Trash modinfo.
686 rebuild_course_cache($course->id, true);
687
688 // Create some cms for testing.
689 $cmids = array();
690 for ($i=0; $i<4; $i++) {
691 $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
692 }
693
694 // Add it to section that exists.
695 course_add_cm_to_section($course, $cmids[0], 1);
696
697 // Check it got added to sequence.
698 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
699 $this->assertEquals($cmids[0], $sequence);
700
701 // Add a second, this time using courseid variant of parameters.
4a3fb71c 702 $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
5523c344 703 course_add_cm_to_section($course->id, $cmids[1], 1);
704 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
705 $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
706
4a3fb71c
MG
707 // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
708 $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
709 $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
5523c344 710
711 // Add one to section that doesn't exist (this might rebuild modinfo).
712 course_add_cm_to_section($course, $cmids[2], 2);
713 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
714 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
715 $this->assertEquals($cmids[2], $sequence);
716
717 // Add using the 'before' option.
718 course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
719 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
720 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
721 $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
722 }
723
354b214c
PS
724 public function test_reorder_sections() {
725 global $DB;
726 $this->resetAfterTest(true);
727
728 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
729 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
730 $oldsections = array();
731 $sections = array();
3a222db2 732 foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
354b214c
PS
733 $oldsections[$section->section] = $section->id;
734 $sections[$section->id] = $section->section;
735 }
736 ksort($oldsections);
737
738 $neworder = reorder_sections($sections, 2, 4);
739 $neworder = array_keys($neworder);
740 $this->assertEquals($oldsections[0], $neworder[0]);
741 $this->assertEquals($oldsections[1], $neworder[1]);
742 $this->assertEquals($oldsections[2], $neworder[4]);
743 $this->assertEquals($oldsections[3], $neworder[2]);
744 $this->assertEquals($oldsections[4], $neworder[3]);
745 $this->assertEquals($oldsections[5], $neworder[5]);
746 $this->assertEquals($oldsections[6], $neworder[6]);
747
eb01aa2c
RT
748 $neworder = reorder_sections($sections, 4, 2);
749 $neworder = array_keys($neworder);
750 $this->assertEquals($oldsections[0], $neworder[0]);
751 $this->assertEquals($oldsections[1], $neworder[1]);
752 $this->assertEquals($oldsections[2], $neworder[3]);
753 $this->assertEquals($oldsections[3], $neworder[4]);
754 $this->assertEquals($oldsections[4], $neworder[2]);
755 $this->assertEquals($oldsections[5], $neworder[5]);
756 $this->assertEquals($oldsections[6], $neworder[6]);
757
354b214c
PS
758 $neworder = reorder_sections(1, 2, 4);
759 $this->assertFalse($neworder);
760 }
761
3d8fe482 762 public function test_move_section_down() {
354b214c
PS
763 global $DB;
764 $this->resetAfterTest(true);
765
766 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
767 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
768 $oldsections = array();
769 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
770 $oldsections[$section->section] = $section->id;
771 }
772 ksort($oldsections);
773
3d8fe482 774 // Test move section down..
354b214c
PS
775 move_section_to($course, 2, 4);
776 $sections = array();
777 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
778 $sections[$section->section] = $section->id;
779 }
780 ksort($sections);
781
782 $this->assertEquals($oldsections[0], $sections[0]);
783 $this->assertEquals($oldsections[1], $sections[1]);
784 $this->assertEquals($oldsections[2], $sections[4]);
785 $this->assertEquals($oldsections[3], $sections[2]);
786 $this->assertEquals($oldsections[4], $sections[3]);
787 $this->assertEquals($oldsections[5], $sections[5]);
788 $this->assertEquals($oldsections[6], $sections[6]);
789 }
790
3d8fe482
DP
791 public function test_move_section_up() {
792 global $DB;
793 $this->resetAfterTest(true);
794
795 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
796 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
797 $oldsections = array();
798 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
799 $oldsections[$section->section] = $section->id;
800 }
801 ksort($oldsections);
802
803 // Test move section up..
804 move_section_to($course, 6, 4);
805 $sections = array();
806 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
807 $sections[$section->section] = $section->id;
808 }
809 ksort($sections);
810
811 $this->assertEquals($oldsections[0], $sections[0]);
812 $this->assertEquals($oldsections[1], $sections[1]);
813 $this->assertEquals($oldsections[2], $sections[2]);
814 $this->assertEquals($oldsections[3], $sections[3]);
815 $this->assertEquals($oldsections[4], $sections[5]);
816 $this->assertEquals($oldsections[5], $sections[6]);
817 $this->assertEquals($oldsections[6], $sections[4]);
818 }
819
820 public function test_move_section_marker() {
821 global $DB;
822 $this->resetAfterTest(true);
823
824 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
825 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
826
827 // Set course marker to the section we are going to move..
828 course_set_marker($course->id, 2);
829 // Verify that the course marker is set correctly.
830 $course = $DB->get_record('course', array('id' => $course->id));
831 $this->assertEquals(2, $course->marker);
832
833 // Test move the marked section down..
834 move_section_to($course, 2, 4);
835
836 // Verify that the coruse marker has been moved along with the section..
837 $course = $DB->get_record('course', array('id' => $course->id));
838 $this->assertEquals(4, $course->marker);
839
840 // Test move the marked section up..
841 move_section_to($course, 4, 3);
842
843 // Verify that the course marker has been moved along with the section..
844 $course = $DB->get_record('course', array('id' => $course->id));
845 $this->assertEquals(3, $course->marker);
846
847 // Test moving a non-marked section above the marked section..
848 move_section_to($course, 4, 2);
849
850 // Verify that the course marker has been moved down to accomodate..
851 $course = $DB->get_record('course', array('id' => $course->id));
852 $this->assertEquals(4, $course->marker);
853
854 // Test moving a non-marked section below the marked section..
855 move_section_to($course, 3, 6);
856
857 // Verify that the course marker has been up to accomodate..
858 $course = $DB->get_record('course', array('id' => $course->id));
859 $this->assertEquals(3, $course->marker);
860 }
861
354b214c
PS
862 public function test_get_course_display_name_for_list() {
863 global $CFG;
864 $this->resetAfterTest(true);
865
866 $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
867
868 $CFG->courselistshortnames = 0;
869 $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
870
871 $CFG->courselistshortnames = 1;
872 $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
873 }
b1a8aa73 874
384c3510 875 public function test_move_module_in_course() {
3f61e4cb
ARN
876 global $DB;
877
384c3510
MG
878 $this->resetAfterTest(true);
879 // Setup fixture
3f61e4cb 880 $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
384c3510
MG
881 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
882
384c3510
MG
883 $cms = get_fast_modinfo($course)->get_cms();
884 $cm = reset($cms);
885
3f61e4cb
ARN
886 $newsection = get_fast_modinfo($course)->get_section_info(3);
887 $oldsectionid = $cm->section;
888
889 // Perform the move
890 moveto_module($cm, $newsection);
384c3510 891
3f61e4cb
ARN
892 $cms = get_fast_modinfo($course)->get_cms();
893 $cm = reset($cms);
384c3510 894
3f61e4cb 895 // Check that the cached modinfo contains the correct section info
384c3510
MG
896 $modinfo = get_fast_modinfo($course);
897 $this->assertTrue(empty($modinfo->sections[0]));
898 $this->assertFalse(empty($modinfo->sections[3]));
3f61e4cb
ARN
899
900 // Check that the old section's sequence no longer contains this ID
901 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
902 $oldsequences = explode(',', $newsection->sequence);
903 $this->assertFalse(in_array($cm->id, $oldsequences));
904
905 // Check that the new section's sequence now contains this ID
906 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
907 $newsequences = explode(',', $newsection->sequence);
908 $this->assertTrue(in_array($cm->id, $newsequences));
909
910 // Check that the section number has been changed in the cm
911 $this->assertEquals($newsection->id, $cm->section);
912
913
914 // Perform a second move as some issues were only seen on the second move
915 $newsection = get_fast_modinfo($course)->get_section_info(2);
916 $oldsectionid = $cm->section;
d55f05ef 917 moveto_module($cm, $newsection);
3f61e4cb 918
3f61e4cb
ARN
919 $cms = get_fast_modinfo($course)->get_cms();
920 $cm = reset($cms);
921
922 // Check that the cached modinfo contains the correct section info
923 $modinfo = get_fast_modinfo($course);
924 $this->assertTrue(empty($modinfo->sections[0]));
925 $this->assertFalse(empty($modinfo->sections[2]));
926
927 // Check that the old section's sequence no longer contains this ID
928 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
929 $oldsequences = explode(',', $newsection->sequence);
930 $this->assertFalse(in_array($cm->id, $oldsequences));
931
932 // Check that the new section's sequence now contains this ID
933 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
934 $newsequences = explode(',', $newsection->sequence);
935 $this->assertTrue(in_array($cm->id, $newsequences));
384c3510 936 }
f7d6e650
FM
937
938 public function test_module_visibility() {
939 $this->setAdminUser();
940 $this->resetAfterTest(true);
941
942 // Create course and modules.
943 $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
944 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
945 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
946 $modules = compact('forum', 'assign');
947
948 // Hiding the modules.
949 foreach ($modules as $mod) {
950 set_coursemodule_visible($mod->cmid, 0);
951 $this->check_module_visibility($mod, 0, 0);
952 }
953
954 // Showing the modules.
955 foreach ($modules as $mod) {
956 set_coursemodule_visible($mod->cmid, 1);
957 $this->check_module_visibility($mod, 1, 1);
958 }
959 }
960
9e533215
SL
961 public function test_section_visibility_events() {
962 $this->setAdminUser();
963 $this->resetAfterTest(true);
964
965 $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
966 $sectionnumber = 1;
967 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
968 array('section' => $sectionnumber));
969 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
970 'course' => $course->id), array('section' => $sectionnumber));
971 $sink = $this->redirectEvents();
972 set_section_visible($course->id, $sectionnumber, 0);
973 $events = $sink->get_events();
974
975 // Extract the number of events related to what we are testing, other events
976 // such as course_section_updated could have been triggered.
977 $count = 0;
978 foreach ($events as $event) {
979 if ($event instanceof \core\event\course_module_updated) {
980 $count++;
981 }
982 }
983 $this->assertSame(2, $count);
984 $sink->close();
985 }
986
f7d6e650
FM
987 public function test_section_visibility() {
988 $this->setAdminUser();
989 $this->resetAfterTest(true);
990
991 // Create course.
992 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
993
1f6988bb
FM
994 $sink = $this->redirectEvents();
995
f7d6e650
FM
996 // Testing an empty section.
997 $sectionnumber = 1;
998 set_section_visible($course->id, $sectionnumber, 0);
999 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1000 $this->assertEquals($section_info->visible, 0);
1001 set_section_visible($course->id, $sectionnumber, 1);
1002 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1003 $this->assertEquals($section_info->visible, 1);
1004
1f6988bb
FM
1005 // Checking that an event was fired.
1006 $events = $sink->get_events();
1007 $this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
1008 $sink->close();
1009
f7d6e650
FM
1010 // Testing a section with visible modules.
1011 $sectionnumber = 2;
1012 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1013 array('section' => $sectionnumber));
1014 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1015 'course' => $course->id), array('section' => $sectionnumber));
1016 $modules = compact('forum', 'assign');
1017 set_section_visible($course->id, $sectionnumber, 0);
1018 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1019 $this->assertEquals($section_info->visible, 0);
1020 foreach ($modules as $mod) {
1021 $this->check_module_visibility($mod, 0, 1);
1022 }
1023 set_section_visible($course->id, $sectionnumber, 1);
1024 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1025 $this->assertEquals($section_info->visible, 1);
1026 foreach ($modules as $mod) {
1027 $this->check_module_visibility($mod, 1, 1);
1028 }
1029
1030 // Testing a section with hidden modules, which should stay hidden.
1031 $sectionnumber = 3;
1032 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1033 array('section' => $sectionnumber));
1034 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1035 'course' => $course->id), array('section' => $sectionnumber));
1036 $modules = compact('forum', 'assign');
1037 foreach ($modules as $mod) {
1038 set_coursemodule_visible($mod->cmid, 0);
1039 $this->check_module_visibility($mod, 0, 0);
1040 }
1041 set_section_visible($course->id, $sectionnumber, 0);
1042 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1043 $this->assertEquals($section_info->visible, 0);
1044 foreach ($modules as $mod) {
1045 $this->check_module_visibility($mod, 0, 0);
1046 }
1047 set_section_visible($course->id, $sectionnumber, 1);
1048 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1049 $this->assertEquals($section_info->visible, 1);
1050 foreach ($modules as $mod) {
1051 $this->check_module_visibility($mod, 0, 0);
1052 }
1053 }
1054
1055 /**
1056 * Helper function to assert that a module has correctly been made visible, or hidden.
1057 *
1058 * @param stdClass $mod module information
1059 * @param int $visibility the current state of the module
1060 * @param int $visibleold the current state of the visibleold property
1061 * @return void
1062 */
1063 public function check_module_visibility($mod, $visibility, $visibleold) {
1064 global $DB;
1065 $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
1066 $this->assertEquals($visibility, $cm->visible);
1067 $this->assertEquals($visibleold, $cm->visibleold);
1068
1069 // Check the module grade items.
1070 $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
1071 'iteminstance' => $cm->instance, 'courseid' => $cm->course));
1072 if ($grade_items) {
1073 foreach ($grade_items as $grade_item) {
1074 if ($visibility) {
1075 $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
1076 } else {
1077 $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
1078 }
1079 }
1080 }
1081
1082 // Check the events visibility.
1083 if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
1084 foreach ($events as $event) {
1085 $calevent = new calendar_event($event);
1086 $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
1087 }
1088 }
1089 }
1090
b7d3bb0f
AG
1091 public function test_course_page_type_list() {
1092 global $DB;
1093 $this->resetAfterTest(true);
1094
1095 // Create a category.
1096 $category = new stdClass();
1097 $category->name = 'Test Category';
1098
1099 $testcategory = $this->getDataGenerator()->create_category($category);
1100
1101 // Create a course.
1102 $course = new stdClass();
1103 $course->fullname = 'Apu loves Unit Təsts';
1104 $course->shortname = 'Spread the lŭve';
1105 $course->idnumber = '123';
1106 $course->summary = 'Awesome!';
1107 $course->summaryformat = FORMAT_PLAIN;
1108 $course->format = 'topics';
1109 $course->newsitems = 0;
1110 $course->numsections = 5;
1111 $course->category = $testcategory->id;
1112
1113 $testcourse = $this->getDataGenerator()->create_course($course);
1114
1115 // Create contexts.
1116 $coursecontext = context_course::instance($testcourse->id);
1117 $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
1118 $pagetype = 'page-course-x'; // Not used either.
1119 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
1120
1121 // Page type lists for normal courses.
1122 $testpagetypelist1 = array();
1123 $testpagetypelist1['*'] = 'Any page';
1124 $testpagetypelist1['course-*'] = 'Any course page';
1125 $testpagetypelist1['course-view-*'] = 'Any type of course main page';
1126
1127 $this->assertEquals($testpagetypelist1, $pagetypelist);
1128
1129 // Get the context for the front page course.
1130 $sitecoursecontext = context_course::instance(SITEID);
1131 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
1132
1133 // Page type list for the front page course.
1134 $testpagetypelist2 = array('*' => 'Any page');
1135 $this->assertEquals($testpagetypelist2, $pagetypelist);
1136
1137 // Make sure that providing no current context to the function doesn't result in an error.
1138 // Calls made from generate_page_type_patterns() may provide null values.
1139 $pagetypelist = course_page_type_list($pagetype, null, null);
1140 $this->assertEquals($pagetypelist, $testpagetypelist1);
1141 }
bf6b3c7a
AA
1142
1143 public function test_compare_activities_by_time_desc() {
1144
1145 // Let's create some test data.
1146 $activitiesivities = array();
1147 $x = new stdClass();
1148 $x->timestamp = null;
1149 $activities[] = $x;
1150
1151 $x = new stdClass();
1152 $x->timestamp = 1;
1153 $activities[] = $x;
1154
1155 $x = new stdClass();
1156 $x->timestamp = 3;
1157 $activities[] = $x;
1158
1159 $x = new stdClass();
1160 $x->timestamp = 0;
1161 $activities[] = $x;
1162
1163 $x = new stdClass();
1164 $x->timestamp = 5;
1165 $activities[] = $x;
1166
1167 $x = new stdClass();
1168 $activities[] = $x;
1169
1170 $x = new stdClass();
1171 $x->timestamp = 5;
1172 $activities[] = $x;
1173
1174 // Do the sorting.
1175 usort($activities, 'compare_activities_by_time_desc');
1176
1177 // Let's check the result.
1178 $last = 10;
1179 foreach($activities as $activity) {
1180 if (empty($activity->timestamp)) {
1181 $activity->timestamp = 0;
1182 }
1183 $this->assertLessThanOrEqual($last, $activity->timestamp);
1184 }
1185 }
1186
1187 public function test_compare_activities_by_time_asc() {
1188
1189 // Let's create some test data.
1190 $activities = array();
1191 $x = new stdClass();
1192 $x->timestamp = null;
1193 $activities[] = $x;
1194
1195 $x = new stdClass();
1196 $x->timestamp = 1;
1197 $activities[] = $x;
1198
1199 $x = new stdClass();
1200 $x->timestamp = 3;
1201 $activities[] = $x;
1202
1203 $x = new stdClass();
1204 $x->timestamp = 0;
1205 $activities[] = $x;
1206
1207 $x = new stdClass();
1208 $x->timestamp = 5;
1209 $activities[] = $x;
1210
1211 $x = new stdClass();
1212 $activities[] = $x;
1213
1214 $x = new stdClass();
1215 $x->timestamp = 5;
1216 $activities[] = $x;
1217
1218 // Do the sorting.
1219 usort($activities, 'compare_activities_by_time_asc');
1220
1221 // Let's check the result.
1222 $last = 0;
1223 foreach($activities as $activity) {
1224 if (empty($activity->timestamp)) {
1225 $activity->timestamp = 0;
1226 }
1227 $this->assertGreaterThanOrEqual($last, $activity->timestamp);
1228 }
1229 }
9ab0aece 1230
1fff1b8c
DP
1231 /**
1232 * Tests moving a module between hidden/visible sections and
1233 * verifies that the course/module visiblity seettings are
1234 * retained.
1235 */
1236 public function test_moveto_module_between_hidden_sections() {
1237 global $DB;
1238
1239 $this->resetAfterTest(true);
1240
1241 $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
1242 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1243 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1244 $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
1245
1246 // Set the page as hidden
1247 set_coursemodule_visible($page->cmid, 0);
1248
1249 // Set sections 3 as hidden.
1250 set_section_visible($course->id, 3, 0);
1251
1252 $modinfo = get_fast_modinfo($course);
1253
1254 $hiddensection = $modinfo->get_section_info(3);
1255 // New section is definitely not visible:
1256 $this->assertEquals($hiddensection->visible, 0);
1257
1258 $forumcm = $modinfo->cms[$forum->cmid];
1259 $pagecm = $modinfo->cms[$page->cmid];
1260
d55f05ef
MG
1261 // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
1262 $this->assertEquals(0, moveto_module($forumcm, $hiddensection));
1263 $this->assertEquals(0, moveto_module($pagecm, $hiddensection));
1fff1b8c 1264
1fff1b8c
DP
1265 $modinfo = get_fast_modinfo($course);
1266
1267 // Verify that forum and page have been moved to the hidden section and quiz has not.
1268 $this->assertContains($forum->cmid, $modinfo->sections[3]);
1269 $this->assertContains($page->cmid, $modinfo->sections[3]);
1270 $this->assertNotContains($quiz->cmid, $modinfo->sections[3]);
1271
1272 // Verify that forum has been made invisible.
1273 $forumcm = $modinfo->cms[$forum->cmid];
1274 $this->assertEquals($forumcm->visible, 0);
1275 // Verify that old state has been retained.
1276 $this->assertEquals($forumcm->visibleold, 1);
1277
1278 // Verify that page has stayed invisible.
1279 $pagecm = $modinfo->cms[$page->cmid];
1280 $this->assertEquals($pagecm->visible, 0);
1281 // Verify that old state has been retained.
1282 $this->assertEquals($pagecm->visibleold, 0);
1283
1284 // Verify that quiz has been unaffected.
1285 $quizcm = $modinfo->cms[$quiz->cmid];
1286 $this->assertEquals($quizcm->visible, 1);
1287
1288 // Move forum and page back to visible section.
d55f05ef 1289 // Make sure the visibility is restored to the original value (visible for forum and hidden for page).
1fff1b8c 1290 $visiblesection = $modinfo->get_section_info(2);
d55f05ef
MG
1291 $this->assertEquals(1, moveto_module($forumcm, $visiblesection));
1292 $this->assertEquals(0, moveto_module($pagecm, $visiblesection));
1fff1b8c 1293
1fff1b8c
DP
1294 $modinfo = get_fast_modinfo($course);
1295
d55f05ef 1296 // Double check that forum has been made visible.
1fff1b8c
DP
1297 $forumcm = $modinfo->cms[$forum->cmid];
1298 $this->assertEquals($forumcm->visible, 1);
1299
d55f05ef 1300 // Double check that page has stayed invisible.
1fff1b8c
DP
1301 $pagecm = $modinfo->cms[$page->cmid];
1302 $this->assertEquals($pagecm->visible, 0);
1303
d55f05ef
MG
1304 // Move the page in the same section (this is what mod duplicate does).
1305 // Visibility of page remains 0.
1306 $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
1fff1b8c 1307
d55f05ef 1308 // Double check that the the page is still hidden.
1fff1b8c
DP
1309 $modinfo = get_fast_modinfo($course);
1310 $pagecm = $modinfo->cms[$page->cmid];
1311 $this->assertEquals($pagecm->visible, 0);
1312 }
1313
1314 /**
1315 * Tests moving a module around in the same section. moveto_module()
1316 * is called this way in modduplicate.
1317 */
1318 public function test_moveto_module_in_same_section() {
1319 global $DB;
1320
1321 $this->resetAfterTest(true);
1322
1323 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
1324 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1325 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1326
1327 // Simulate inconsistent visible/visibleold values (MDL-38713).
1328 $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
1329 $cm->visible = 0;
1330 $cm->visibleold = 1;
1331 $DB->update_record('course_modules', $cm);
1332
1333 $modinfo = get_fast_modinfo($course);
1334 $forumcm = $modinfo->cms[$forum->cmid];
1335 $pagecm = $modinfo->cms[$page->cmid];
1336
1337 // Verify that page is hidden.
1338 $this->assertEquals($pagecm->visible, 0);
1339
1340 // Verify section 0 is where all mods added.
1341 $section = $modinfo->get_section_info(0);
1342 $this->assertEquals($section->id, $forumcm->section);
1343 $this->assertEquals($section->id, $pagecm->section);
1344
1345
d55f05ef
MG
1346 // Move the page inside the hidden section. Make sure it is hidden.
1347 $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
1fff1b8c 1348
d55f05ef 1349 // Double check that the the page is still hidden.
1fff1b8c
DP
1350 $modinfo = get_fast_modinfo($course);
1351 $pagecm = $modinfo->cms[$page->cmid];
1352 $this->assertEquals($pagecm->visible, 0);
1353 }
e52c1ea6
DP
1354
1355 public function test_course_delete_module() {
1356 global $DB;
1357 $this->resetAfterTest(true);
1358 $this->setAdminUser();
1359
1360 // Create course and modules.
1361 $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
1362
1363 // Generate an assignment with due date (will generate a course event).
1364 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
1365
cc033d48
MN
1366 // Get the module context.
1367 $modcontext = context_module::instance($assign->cmid);
e52c1ea6
DP
1368
1369 // Verify context exists.
cc033d48
MN
1370 $this->assertInstanceOf('context_module', $modcontext);
1371
1372 // Add some tags to this assignment.
1373 tag_set('assign', $assign->id, array('Tag 1', 'Tag 2', 'Tag 3'), 'mod_assign', $modcontext->id);
1374
1375 // Confirm the tag instances were added.
1376 $this->assertEquals(3, $DB->count_records('tag_instance', array('component' => 'mod_assign', 'contextid' =>
1377 $modcontext->id)));
e52c1ea6
DP
1378
1379 // Verify event assignment event has been generated.
1380 $eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
1381 $this->assertEquals(1, $eventcount);
1382
1383 // Run delete..
cc033d48 1384 course_delete_module($assign->cmid);
e52c1ea6
DP
1385
1386 // Verify the context has been removed.
cc033d48 1387 $this->assertFalse(context_module::instance($assign->cmid, IGNORE_MISSING));
e52c1ea6
DP
1388
1389 // Verify the course_module record has been deleted.
cc033d48 1390 $cmcount = $DB->count_records('course_modules', array('id' => $assign->cmid));
e52c1ea6
DP
1391 $this->assertEmpty($cmcount);
1392
1393 // Verify event assignment events have been removed.
1394 $eventcount = $DB->count_records('event', array('instance' => $assign->id, 'modulename' => 'assign'));
1395 $this->assertEmpty($eventcount);
cc033d48
MN
1396
1397 // Verify the tag instances were deleted.
1398 $this->assertEquals(0, $DB->count_records('tag_instance', array('component' => 'mod_assign', 'contextid' =>
1399 $modcontext->id)));
e52c1ea6 1400 }
35ad79e2
MN
1401
1402 /**
1403 * Test that triggering a course_created event works as expected.
1404 */
1405 public function test_course_created_event() {
3a11e2d2
RT
1406 global $DB;
1407
35ad79e2
MN
1408 $this->resetAfterTest();
1409
1410 // Catch the events.
1411 $sink = $this->redirectEvents();
1412
7cbb5070
MN
1413 // Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
1414 $data = new stdClass();
1415 $data->idnumber = 'idnumber';
1416 $course = $this->getDataGenerator()->create_course($data);
3a11e2d2
RT
1417 // Get course from DB for comparison.
1418 $course = $DB->get_record('course', array('id' => $course->id));
35ad79e2
MN
1419
1420 // Capture the event.
1421 $events = $sink->get_events();
1422 $sink->close();
1423
1424 // Validate the event.
1425 $event = $events[0];
1426 $this->assertInstanceOf('\core\event\course_created', $event);
1427 $this->assertEquals('course', $event->objecttable);
1428 $this->assertEquals($course->id, $event->objectid);
3a11e2d2 1429 $this->assertEquals(context_course::instance($course->id), $event->get_context());
35ad79e2
MN
1430 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1431 $this->assertEquals('course_created', $event->get_legacy_eventname());
1432 $this->assertEventLegacyData($course, $event);
1433 $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
1434 $this->assertEventLegacyLogData($expectedlog, $event);
7cbb5070
MN
1435
1436 // Now we want to trigger creating a course via the imsenterprise.
1437 // Delete the course we created earlier, as we want the imsenterprise plugin to create this.
1438 // We do not want print out any of the text this function generates while doing this, which is why
1439 // we are using ob_start() and ob_end_clean().
1440 ob_start();
1441 delete_course($course);
1442 ob_end_clean();
1443
1444 // Create the XML file we want to use.
1445 $imstestcase = new enrol_imsenterprise_testcase();
1446 $imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
1447 $imstestcase->set_test_config();
1448 $imstestcase->set_xml_file(false, array($course));
1449
1450 // Capture the event.
1451 $sink = $this->redirectEvents();
1452 $imstestcase->imsplugin->cron();
1453 $events = $sink->get_events();
1454 $sink->close();
1455 $event = $events[0];
1456
1457 // Validate the event triggered is \core\event\course_created. There is no need to validate the other values
1458 // as they have already been validated in the previous steps. Here we only want to make sure that when the
1459 // imsenterprise plugin creates a course an event is triggered.
1460 $this->assertInstanceOf('\core\event\course_created', $event);
623a32e5 1461 $this->assertEventContextNotUsed($event);
35ad79e2 1462 }
4fd391d5
MN
1463
1464 /**
1465 * Test that triggering a course_updated event works as expected.
1466 */
1467 public function test_course_updated_event() {
1468 global $DB;
1469
1470 $this->resetAfterTest();
1471
e5959771 1472 // Create a course.
4fd391d5
MN
1473 $course = $this->getDataGenerator()->create_course();
1474
e5959771
MN
1475 // Create a category we are going to move this course to.
1476 $category = $this->getDataGenerator()->create_category();
1477
aec8fe2f
MG
1478 // Create a hidden category we are going to move this course to.
1479 $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
1480
3a11e2d2 1481 // Update course and catch course_updated event.
4fd391d5 1482 $sink = $this->redirectEvents();
4fd391d5 1483 update_course($course);
4fd391d5
MN
1484 $events = $sink->get_events();
1485 $sink->close();
1486
3a11e2d2
RT
1487 // Get updated course information from the DB.
1488 $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1489 // Validate event.
1490 $event = array_shift($events);
4fd391d5
MN
1491 $this->assertInstanceOf('\core\event\course_updated', $event);
1492 $this->assertEquals('course', $event->objecttable);
e5959771 1493 $this->assertEquals($updatedcourse->id, $event->objectid);
3a11e2d2 1494 $this->assertEquals(context_course::instance($course->id), $event->get_context());
b63f7732
AA
1495 $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
1496 $this->assertEquals($url, $event->get_url());
3a11e2d2 1497 $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
4fd391d5 1498 $this->assertEquals('course_updated', $event->get_legacy_eventname());
e5959771
MN
1499 $this->assertEventLegacyData($updatedcourse, $event);
1500 $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
1501 $this->assertEventLegacyLogData($expectedlog, $event);
1502
3a11e2d2
RT
1503 // Move course and catch course_updated event.
1504 $sink = $this->redirectEvents();
1505 move_courses(array($course->id), $category->id);
1506 $events = $sink->get_events();
1507 $sink->close();
1508
1509 // Return the moved course information from the DB.
1510 $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1511 // Validate event.
1512 $event = array_shift($events);
e5959771
MN
1513 $this->assertInstanceOf('\core\event\course_updated', $event);
1514 $this->assertEquals('course', $event->objecttable);
1515 $this->assertEquals($movedcourse->id, $event->objectid);
3a11e2d2 1516 $this->assertEquals(context_course::instance($course->id), $event->get_context());
e5959771
MN
1517 $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
1518 $this->assertEquals('course_updated', $event->get_legacy_eventname());
9e2d9135 1519 $this->assertEventLegacyData($movedcourse, $event);
e5959771 1520 $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
4fd391d5 1521 $this->assertEventLegacyLogData($expectedlog, $event);
aec8fe2f 1522
3a11e2d2
RT
1523 // Move course to hidden category and catch course_updated event.
1524 $sink = $this->redirectEvents();
1525 move_courses(array($course->id), $categoryhidden->id);
1526 $events = $sink->get_events();
1527 $sink->close();
1528
1529 // Return the moved course information from the DB.
1530 $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1531 // Validate event.
1532 $event = array_shift($events);
aec8fe2f
MG
1533 $this->assertInstanceOf('\core\event\course_updated', $event);
1534 $this->assertEquals('course', $event->objecttable);
1535 $this->assertEquals($movedcoursehidden->id, $event->objectid);
3a11e2d2 1536 $this->assertEquals(context_course::instance($course->id), $event->get_context());
aec8fe2f
MG
1537 $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
1538 $this->assertEquals('course_updated', $event->get_legacy_eventname());
1539 $this->assertEventLegacyData($movedcoursehidden, $event);
1540 $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
1541 $this->assertEventLegacyLogData($expectedlog, $event);
623a32e5 1542 $this->assertEventContextNotUsed($event);
4fd391d5 1543 }
bc3c5b22
MN
1544
1545 /**
1546 * Test that triggering a course_deleted event works as expected.
1547 */
1548 public function test_course_deleted_event() {
1549 $this->resetAfterTest();
1550
1551 // Create the course.
1552 $course = $this->getDataGenerator()->create_course();
1553
1554 // Save the course context before we delete the course.
1555 $coursecontext = context_course::instance($course->id);
1556
1557 // Catch the update event.
1558 $sink = $this->redirectEvents();
1559
1560 // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
1561 // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
1562 // so use ob_start and ob_end_clean to prevent this.
1563 ob_start();
1564 delete_course($course);
1565 ob_end_clean();
1566
1567 // Capture the event.
1568 $events = $sink->get_events();
1569 $sink->close();
1570
1571 // Validate the event.
ec8f23de 1572 $event = $events[1];
bc3c5b22
MN
1573 $this->assertInstanceOf('\core\event\course_deleted', $event);
1574 $this->assertEquals('course', $event->objecttable);
1575 $this->assertEquals($course->id, $event->objectid);
1576 $this->assertEquals($coursecontext->id, $event->contextid);
1577 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1578 $this->assertEquals('course_deleted', $event->get_legacy_eventname());
0d22c248
RT
1579 $eventdata = $event->get_data();
1580 $this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
1581 $this->assertSame($course->fullname, $eventdata['other']['fullname']);
1582 $this->assertSame($course->shortname, $eventdata['other']['shortname']);
ae29096b
MG
1583
1584 // The legacy data also passed the context in the course object and substitutes timemodified with the current date.
1585 $expectedlegacy = clone($course);
1586 $expectedlegacy->context = $coursecontext;
1587 $expectedlegacy->timemodified = $event->timecreated;
1588 $this->assertEventLegacyData($expectedlegacy, $event);
1589
1590 // Validate legacy log data.
bc3c5b22
MN
1591 $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
1592 $this->assertEventLegacyLogData($expectedlog, $event);
623a32e5 1593 $this->assertEventContextNotUsed($event);
bc3c5b22 1594 }
ec8f23de
MN
1595
1596 /**
1597 * Test that triggering a course_content_deleted event works as expected.
1598 */
1599 public function test_course_content_deleted_event() {
1600 global $DB;
1601
1602 $this->resetAfterTest();
1603
1604 // Create the course.
1605 $course = $this->getDataGenerator()->create_course();
1606
1607 // Get the course from the DB. The data generator adds some extra properties, such as
1608 // numsections, to the course object which will fail the assertions later on.
1609 $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1610
1611 // Save the course context before we delete the course.
1612 $coursecontext = context_course::instance($course->id);
1613
1614 // Catch the update event.
1615 $sink = $this->redirectEvents();
1616
542d3820 1617 remove_course_contents($course->id, false);
ec8f23de
MN
1618
1619 // Capture the event.
1620 $events = $sink->get_events();
1621 $sink->close();
1622
1623 // Validate the event.
1624 $event = $events[0];
1625 $this->assertInstanceOf('\core\event\course_content_deleted', $event);
1626 $this->assertEquals('course', $event->objecttable);
1627 $this->assertEquals($course->id, $event->objectid);
1628 $this->assertEquals($coursecontext->id, $event->contextid);
1629 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1630 $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
1631 // The legacy data also passed the context and options in the course object.
1632 $course->context = $coursecontext;
1633 $course->options = array();
1634 $this->assertEventLegacyData($course, $event);
623a32e5 1635 $this->assertEventContextNotUsed($event);
ec8f23de 1636 }
7240cd92
MN
1637
1638 /**
1639 * Test that triggering a course_category_deleted event works as expected.
1640 */
1641 public function test_course_category_deleted_event() {
1642 $this->resetAfterTest();
1643
1644 // Create a category.
1645 $category = $this->getDataGenerator()->create_category();
1646
1647 // Save the context before it is deleted.
1648 $categorycontext = context_coursecat::instance($category->id);
1649
1650 // Catch the update event.
1651 $sink = $this->redirectEvents();
1652
1653 // Delete the category.
1654 $category->delete_full();
1655
1656 // Capture the event.
1657 $events = $sink->get_events();
1658 $sink->close();
1659
1660 // Validate the event.
1661 $event = $events[0];
1662 $this->assertInstanceOf('\core\event\course_category_deleted', $event);
1663 $this->assertEquals('course_categories', $event->objecttable);
1664 $this->assertEquals($category->id, $event->objectid);
1665 $this->assertEquals($categorycontext->id, $event->contextid);
1666 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
b63f7732 1667 $this->assertEquals(null, $event->get_url());
7240cd92
MN
1668 $this->assertEventLegacyData($category, $event);
1669 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
1670 $this->assertEventLegacyLogData($expectedlog, $event);
1671
1672 // Create two categories.
1673 $category = $this->getDataGenerator()->create_category();
1674 $category2 = $this->getDataGenerator()->create_category();
1675
1676 // Save the context before it is moved and then deleted.
1677 $category2context = context_coursecat::instance($category2->id);
1678
1679 // Catch the update event.
1680 $sink = $this->redirectEvents();
1681
1682 // Move the category.
1683 $category2->delete_move($category->id);
1684
1685 // Capture the event.
1686 $events = $sink->get_events();
1687 $sink->close();
1688
1689 // Validate the event.
1690 $event = $events[0];
1691 $this->assertInstanceOf('\core\event\course_category_deleted', $event);
1692 $this->assertEquals('course_categories', $event->objecttable);
1693 $this->assertEquals($category2->id, $event->objectid);
1694 $this->assertEquals($category2context->id, $event->contextid);
1695 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
9e2d9135 1696 $this->assertEventLegacyData($category2, $event);
7240cd92
MN
1697 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
1698 $this->assertEventLegacyLogData($expectedlog, $event);
623a32e5 1699 $this->assertEventContextNotUsed($event);
7240cd92 1700 }
02cbb621
MN
1701
1702 /**
1703 * Test that triggering a course_restored event works as expected.
1704 */
1705 public function test_course_restored_event() {
1706 global $CFG;
1707
1708 // Get the necessary files to perform backup and restore.
1709 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
1710 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
1711
1712 $this->resetAfterTest();
1713
1714 // Set to admin user.
1715 $this->setAdminUser();
1716
1717 // The user id is going to be 2 since we are the admin user.
1718 $userid = 2;
1719
1720 // Create a course.
1721 $course = $this->getDataGenerator()->create_course();
1722
1723 // Create backup file and save it to the backup location.
1724 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
1725 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
1726 $bc->execute_plan();
1727 $results = $bc->get_results();
1728 $file = $results['backup_destination'];
00219425 1729 $fp = get_file_packer('application/vnd.moodle.backup');
02cbb621
MN
1730 $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
1731 $file->extract_to_pathname($fp, $filepath);
1732 $bc->destroy();
1733 unset($bc);
1734
1735 // Now we want to catch the restore course event.
1736 $sink = $this->redirectEvents();
1737
1738 // Now restore the course to trigger the event.
1739 $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
1740 backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
1741 $rc->execute_precheck();
1742 $rc->execute_plan();
1743
1744 // Capture the event.
1745 $events = $sink->get_events();
1746 $sink->close();
1747
1748 // Validate the event.
1749 $event = $events[0];
1750 $this->assertInstanceOf('\core\event\course_restored', $event);
1751 $this->assertEquals('course', $event->objecttable);
1752 $this->assertEquals($rc->get_courseid(), $event->objectid);
1753 $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
1754 $this->assertEquals('course_restored', $event->get_legacy_eventname());
1755 $legacydata = (object) array(
1756 'courseid' => $rc->get_courseid(),
1757 'userid' => $rc->get_userid(),
1758 'type' => $rc->get_type(),
1759 'target' => $rc->get_target(),
1760 'mode' => $rc->get_mode(),
1761 'operation' => $rc->get_operation(),
1762 'samesite' => $rc->is_samesite()
1763 );
b63f7732
AA
1764 $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
1765 $this->assertEquals($url, $event->get_url());
02cbb621 1766 $this->assertEventLegacyData($legacydata, $event);
623a32e5 1767 $this->assertEventContextNotUsed($event);
02cbb621
MN
1768
1769 // Destroy the resource controller since we are done using it.
1770 $rc->destroy();
1771 unset($rc);
02cbb621 1772 }
5a10d2a7 1773
ed29bf0f
RT
1774 /**
1775 * Test that triggering a course_section_updated event works as expected.
1776 */
1777 public function test_course_section_updated_event() {
1778 global $DB;
1779
1780 $this->resetAfterTest();
1781
1782 // Create the course with sections.
1783 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
1784 $sections = $DB->get_records('course_sections', array('course' => $course->id));
1785
1786 $coursecontext = context_course::instance($course->id);
1787
1788 $section = array_pop($sections);
1789 $section->name = 'Test section';
1790 $section->summary = 'Test section summary';
1791 $DB->update_record('course_sections', $section);
1792
1793 // Trigger an event for course section update.
1794 $event = \core\event\course_section_updated::create(
1795 array(
1796 'objectid' => $section->id,
1797 'courseid' => $course->id,
be9fb211
FM
1798 'context' => context_course::instance($course->id),
1799 'other' => array(
1800 'sectionnum' => $section->section
1801 )
ed29bf0f
RT
1802 )
1803 );
1804 $event->add_record_snapshot('course_sections', $section);
1805 // Trigger and catch event.
1806 $sink = $this->redirectEvents();
1807 $event->trigger();
1808 $events = $sink->get_events();
1809 $sink->close();
1810
1811 // Validate the event.
1812 $event = $events[0];
1813 $this->assertInstanceOf('\core\event\course_section_updated', $event);
1814 $this->assertEquals('course_sections', $event->objecttable);
1815 $this->assertEquals($section->id, $event->objectid);
1816 $this->assertEquals($course->id, $event->courseid);
1817 $this->assertEquals($coursecontext->id, $event->contextid);
be9fb211 1818 $this->assertEquals($section->section, $event->other['sectionnum']);
88c0e2f3 1819 $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
ed29bf0f 1820 $this->assertEquals($expecteddesc, $event->get_description());
b63f7732
AA
1821 $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
1822 $this->assertEquals($url, $event->get_url());
ed29bf0f
RT
1823 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
1824 $id = $section->id;
1825 $sectionnum = $section->section;
1826 $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
1827 $this->assertEventLegacyLogData($expectedlegacydata, $event);
623a32e5 1828 $this->assertEventContextNotUsed($event);
ed29bf0f 1829 }
749ce98e
DW
1830
1831 public function test_course_integrity_check() {
1832 global $DB;
1833
1834 $this->resetAfterTest(true);
1835 $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
1836 array('createsections'=>true));
1837
1838 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1839 array('section' => 0));
1840 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
1841 array('section' => 0));
1842 $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
1843 array('section' => 0));
1844 $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
1845
1846 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1847 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1848 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1849 $this->assertEquals($correctseq, $section0->sequence);
1850 $this->assertEmpty($section1->sequence);
1851 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1852 $this->assertEquals($section0->id, $cms[$page->cmid]->section);
1853 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1854 $this->assertEmpty(course_integrity_check($course->id));
1855
1856 // Now let's make manual change in DB and let course_integrity_check() fix it:
1857
1858 // 1. Module appears twice in one section.
1859 $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
1860 $this->assertEquals(
1861 array('Failed integrity check for course ['. $course->id.
1862 ']. Sequence for course section ['. $section0->id. '] is "'.
1863 $section0->sequence. ','. $page->cmid. '", must be "'.
1864 $section0->sequence. '"'),
1865 course_integrity_check($course->id));
1866 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1867 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1868 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1869 $this->assertEquals($correctseq, $section0->sequence);
1870 $this->assertEmpty($section1->sequence);
1871 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1872 $this->assertEquals($section0->id, $cms[$page->cmid]->section);
1873 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1874
1875 // 2. Module appears in two sections (last section wins).
1876 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
1877 // First message about double mentioning in sequence, second message about wrong section field for $page.
1878 $this->assertEquals(array(
1879 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
1880 '] must be removed from sequence of section ['. $section0->id.
1881 '] because it is also present in sequence of section ['. $section1->id. ']',
1882 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
1883 '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
1884 course_integrity_check($course->id));
1885 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1886 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1887 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1888 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
1889 $this->assertEquals(''. $page->cmid, $section1->sequence);
1890 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1891 $this->assertEquals($section1->id, $cms[$page->cmid]->section);
1892 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1893
1894 // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
1895 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
1896 $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
1897 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1898 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1899 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1900 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
1901 $this->assertEmpty($section1->sequence);
1902 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1903 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
1904 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1905
1906 // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
1907 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
1908 $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
1909 course_integrity_check($course->id, null, null, true)); // Error!
1910 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1911 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1912 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1913 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
1914 $this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section.
1915 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1916 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
1917 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1918
1919 // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
1920 $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
1921 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
1922 $this->assertEquals(array(
1923 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
e2e9cb6a 1924 '] is missing from sequence of section ['. $section0->id. ']',
749ce98e
DW
1925 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
1926 '] points to section [8765] instead of ['. $section0->id. ']'),
1927 course_integrity_check($course->id, null, null, true));
1928 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1929 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1930 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1931 $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
1932 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1933 $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
1934 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1935
1936 // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
1937 $DB->delete_records('course_modules', array('id' => $page->cmid));
1938 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
1939 $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
1940 course_integrity_check($course->id, null, null, true));
1941 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
1942 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
1943 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
1944 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
1945 $this->assertEmpty($section1->sequence);
1946 $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
1947 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
1948 $this->assertEquals(2, count($cms));
1949 }
b9b994c7
AA
1950
1951 /**
1952 * Tests for event related to course module creation.
1953 */
1954 public function test_course_module_created_event() {
1955 global $USER, $DB;
1956 $this->resetAfterTest();
1957
1958 // Create an assign module.
1959 $sink = $this->redirectEvents();
1960 $modinfo = $this->create_specific_module_test('assign');
1961 $events = $sink->get_events();
1962 $event = array_pop($events);
b9b994c7 1963
dcbef3f4 1964 $cm = get_coursemodule_from_id('assign', $modinfo->coursemodule, 0, false, MUST_EXIST);
b9b994c7
AA
1965 $mod = $DB->get_record('assign', array('id' => $modinfo->instance), '*', MUST_EXIST);
1966
1967 // Validate event data.
1968 $this->assertInstanceOf('\core\event\course_module_created', $event);
1969 $this->assertEquals($cm->id, $event->objectid);
1970 $this->assertEquals($USER->id, $event->userid);
1971 $this->assertEquals('course_modules', $event->objecttable);
df9a6c01 1972 $url = new moodle_url('/mod/assign/view.php', array('id' => $cm->id));
b9b994c7
AA
1973 $this->assertEquals($url, $event->get_url());
1974
1975 // Test legacy data.
1976 $this->assertSame('mod_created', $event->get_legacy_eventname());
1977 $eventdata = new stdClass();
1978 $eventdata->modulename = 'assign';
1979 $eventdata->name = $mod->name;
1980 $eventdata->cmid = $cm->id;
1981 $eventdata->courseid = $cm->course;
1982 $eventdata->userid = $USER->id;
1983 $this->assertEventLegacyData($eventdata, $event);
1984
158379e1
MG
1985 $arr = array(
1986 array($cm->course, "course", "add mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"),
1987 array($cm->course, "assign", "add", "view.php?id=$cm->id", $cm->instance, $cm->id)
1988 );
b9b994c7 1989 $this->assertEventLegacyLogData($arr, $event);
623a32e5 1990 $this->assertEventContextNotUsed($event);
b9b994c7 1991
dcbef3f4
AA
1992 // Let us see if duplicating an activity results in a nice course module created event.
1993 $sink->clear();
1994 $course = get_course($mod->course);
1995 $newcm = duplicate_module($course, $cm);
1996 $events = $sink->get_events();
1997 $event = array_pop($events);
1998 $sink->close();
1999
2000 // Validate event data.
2001 $this->assertInstanceOf('\core\event\course_module_created', $event);
2002 $this->assertEquals($newcm->id, $event->objectid);
2003 $this->assertEquals($USER->id, $event->userid);
2004 $this->assertEquals($course->id, $event->courseid);
2005 $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
2006 $this->assertEquals($url, $event->get_url());
b9b994c7
AA
2007 }
2008
2009 /**
2010 * Tests for event validations related to course module creation.
2011 */
2012 public function test_course_module_created_event_exceptions() {
2013
2014 $this->resetAfterTest();
2015
2016 // Generate data.
2017 $modinfo = $this->create_specific_module_test('assign');
2018 $context = context_module::instance($modinfo->coursemodule);
2019
2020 // Test not setting instanceid.
2021 try {
2022 $event = \core\event\course_module_created::create(array(
2023 'courseid' => $modinfo->course,
2024 'context' => $context,
2025 'objectid' => $modinfo->coursemodule,
2026 'other' => array(
2027 'modulename' => 'assign',
2028 'name' => 'My assignment',
2029 )
2030 ));
2031 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2032 other['instanceid']");
2033 } catch (coding_exception $e) {
02a5a4b2 2034 $this->assertContains("The 'instanceid' value must be set in other.", $e->getMessage());
b9b994c7
AA
2035 }
2036
2037 // Test not setting modulename.
2038 try {
2039 $event = \core\event\course_module_created::create(array(
2040 'courseid' => $modinfo->course,
2041 'context' => $context,
2042 'objectid' => $modinfo->coursemodule,
2043 'other' => array(
2044 'instanceid' => $modinfo->instance,
2045 'name' => 'My assignment',
2046 )
2047 ));
2048 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2049 other['modulename']");
2050 } catch (coding_exception $e) {
02a5a4b2 2051 $this->assertContains("The 'modulename' value must be set in other.", $e->getMessage());
b9b994c7
AA
2052 }
2053
2054 // Test not setting name.
2055
2056 try {
2057 $event = \core\event\course_module_created::create(array(
2058 'courseid' => $modinfo->course,
2059 'context' => $context,
2060 'objectid' => $modinfo->coursemodule,
2061 'other' => array(
2062 'modulename' => 'assign',
2063 'instanceid' => $modinfo->instance,
2064 )
2065 ));
2066 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2067 other['name']");
2068 } catch (coding_exception $e) {
02a5a4b2 2069 $this->assertContains("The 'name' value must be set in other.", $e->getMessage());
b9b994c7
AA
2070 }
2071
2072 }
2073
2074 /**
2075 * Tests for event related to course module updates.
2076 */
2077 public function test_course_module_updated_event() {
2078 global $USER, $DB;
2079 $this->resetAfterTest();
2080
2081 // Update a forum module.
2082 $sink = $this->redirectEvents();
2083 $modinfo = $this->update_specific_module_test('forum');
2084 $events = $sink->get_events();
2085 $event = array_pop($events);
2086 $sink->close();
2087
2088 $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2089 $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
2090
2091 // Validate event data.
2092 $this->assertInstanceOf('\core\event\course_module_updated', $event);
2093 $this->assertEquals($cm->id, $event->objectid);
2094 $this->assertEquals($USER->id, $event->userid);
2095 $this->assertEquals('course_modules', $event->objecttable);
df9a6c01 2096 $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
b9b994c7
AA
2097 $this->assertEquals($url, $event->get_url());
2098
2099 // Test legacy data.
2100 $this->assertSame('mod_updated', $event->get_legacy_eventname());
2101 $eventdata = new stdClass();
2102 $eventdata->modulename = 'forum';
2103 $eventdata->name = $mod->name;
2104 $eventdata->cmid = $cm->id;
2105 $eventdata->courseid = $cm->course;
2106 $eventdata->userid = $USER->id;
2107 $this->assertEventLegacyData($eventdata, $event);
2108
158379e1
MG
2109 $arr = array(
2110 array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"),
2111 array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
2112 );
b9b994c7 2113 $this->assertEventLegacyLogData($arr, $event);
623a32e5 2114 $this->assertEventContextNotUsed($event);
b9b994c7
AA
2115 }
2116
9e533215
SL
2117 /**
2118 * Tests for create_from_cm method.
2119 */
2120 public function test_course_module_create_from_cm() {
2121 $this->resetAfterTest();
2122 $this->setAdminUser();
2123
2124 // Create course and modules.
2125 $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
2126
2127 // Generate an assignment.
2128 $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
2129
2130 // Get the module context.
2131 $modcontext = context_module::instance($assign->cmid);
2132
2133 // Get course module.
2134 $cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST);
2135
2136 // Create an event from course module.
2137 $event = \core\event\course_module_updated::create_from_cm($cm, $modcontext);
2138
2139 // Trigger the events.
2140 $sink = $this->redirectEvents();
2141 $event->trigger();
2142 $events = $sink->get_events();
2143 $event2 = array_pop($events);
2144
2145 // Test event data.
2146 $this->assertInstanceOf('\core\event\course_module_updated', $event);
2147 $this->assertEquals($cm->id, $event2->objectid);
2148 $this->assertEquals($modcontext, $event2->get_context());
2149 $this->assertEquals($cm->modname, $event2->other['modulename']);
2150 $this->assertEquals($cm->instance, $event2->other['instanceid']);
2151 $this->assertEquals($cm->name, $event2->other['name']);
2152 $this->assertEventContextNotUsed($event2);
2153 $this->assertSame('mod_updated', $event2->get_legacy_eventname());
2154 $arr = array(
2155 array($cm->course, "course", "update mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"),
2156 array($cm->course, "assign", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
2157 );
2158 $this->assertEventLegacyLogData($arr, $event);
2159 }
2160
b9b994c7
AA
2161 /**
2162 * Tests for event validations related to course module update.
2163 */
2164 public function test_course_module_updated_event_exceptions() {
2165
2166 $this->resetAfterTest();
2167
2168 // Generate data.
2169 $modinfo = $this->create_specific_module_test('assign');
2170 $context = context_module::instance($modinfo->coursemodule);
2171
2172 // Test not setting instanceid.
2173 try {
2174 $event = \core\event\course_module_updated::create(array(
2175 'courseid' => $modinfo->course,
2176 'context' => $context,
2177 'objectid' => $modinfo->coursemodule,
2178 'other' => array(
2179 'modulename' => 'assign',
2180 'name' => 'My assignment',
2181 )
2182 ));
2183 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2184 other['instanceid']");
2185 } catch (coding_exception $e) {
02a5a4b2 2186 $this->assertContains("The 'instanceid' value must be set in other.", $e->getMessage());
b9b994c7
AA
2187 }
2188
2189 // Test not setting modulename.
2190 try {
2191 $event = \core\event\course_module_updated::create(array(
2192 'courseid' => $modinfo->course,
2193 'context' => $context,
2194 'objectid' => $modinfo->coursemodule,
2195 'other' => array(
2196 'instanceid' => $modinfo->instance,
2197 'name' => 'My assignment',
2198 )
2199 ));
2200 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2201 other['modulename']");
2202 } catch (coding_exception $e) {
02a5a4b2 2203 $this->assertContains("The 'modulename' value must be set in other.", $e->getMessage());
b9b994c7
AA
2204 }
2205
2206 // Test not setting name.
2207
2208 try {
2209 $event = \core\event\course_module_updated::create(array(
2210 'courseid' => $modinfo->course,
2211 'context' => $context,
2212 'objectid' => $modinfo->coursemodule,
2213 'other' => array(
2214 'modulename' => 'assign',
2215 'instanceid' => $modinfo->instance,
2216 )
2217 ));
2218 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2219 other['name']");
2220 } catch (coding_exception $e) {
02a5a4b2 2221 $this->assertContains("The 'name' value must be set in other.", $e->getMessage());
b9b994c7
AA
2222 }
2223
2224 }
2225
2226 /**
2227 * Tests for event related to course module delete.
2228 */
2229 public function test_course_module_deleted_event() {
2230 global $USER, $DB;
2231 $this->resetAfterTest();
2232
2233 // Create and delete a module.
2234 $sink = $this->redirectEvents();
2235 $modinfo = $this->create_specific_module_test('forum');
2236 $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2237 course_delete_module($modinfo->coursemodule);
2238 $events = $sink->get_events();
2239 $event = array_pop($events); // delete module event.;
2240 $sink->close();
2241
2242 // Validate event data.
2243 $this->assertInstanceOf('\core\event\course_module_deleted', $event);
2244 $this->assertEquals($cm->id, $event->objectid);
2245 $this->assertEquals($USER->id, $event->userid);
2246 $this->assertEquals('course_modules', $event->objecttable);
2247 $this->assertEquals(null, $event->get_url());
2248 $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
2249
2250 // Test legacy data.
2251 $this->assertSame('mod_deleted', $event->get_legacy_eventname());
2252 $eventdata = new stdClass();
2253 $eventdata->modulename = 'forum';
2254 $eventdata->cmid = $cm->id;
2255 $eventdata->courseid = $cm->course;
2256 $eventdata->userid = $USER->id;
2257 $this->assertEventLegacyData($eventdata, $event);
2258
2259 $arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id);
2260 $this->assertEventLegacyLogData($arr, $event);
2261
2262 }
2263
2264 /**
2265 * Tests for event validations related to course module deletion.
2266 */
2267 public function test_course_module_deleted_event_exceptions() {
2268
2269 $this->resetAfterTest();
2270
2271 // Generate data.
2272 $modinfo = $this->create_specific_module_test('assign');
2273 $context = context_module::instance($modinfo->coursemodule);
2274
2275 // Test not setting instanceid.
2276 try {
2277 $event = \core\event\course_module_deleted::create(array(
2278 'courseid' => $modinfo->course,
2279 'context' => $context,
2280 'objectid' => $modinfo->coursemodule,
2281 'other' => array(
2282 'modulename' => 'assign',
2283 'name' => 'My assignment',
2284 )
2285 ));
2286 $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2287 other['instanceid']");
2288 } catch (coding_exception $e) {
02a5a4b2 2289 $this->assertContains("The 'instanceid' value must be set in other.", $e->getMessage());
b9b994c7
AA
2290 }
2291
2292 // Test not setting modulename.
2293 try {
2294 $event = \core\event\course_module_deleted::create(array(
2295 'courseid' => $modinfo->course,
2296 'context' => $context,
2297 'objectid' => $modinfo->coursemodule,
2298 'other' => array(
2299 'instanceid' => $modinfo->instance,
2300 'name' => 'My assignment',
2301 )
2302 ));
2303 $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2304 other['modulename']");
2305 } catch (coding_exception $e) {
02a5a4b2 2306 $this->assertContains("The 'modulename' value must be set in other.", $e->getMessage());
b9b994c7
AA
2307 }
2308 }
5dc361e1
SH
2309
2310 /**
2311 * Returns a user object and its assigned new role.
2312 *
2313 * @param testing_data_generator $generator
2314 * @param $contextid
2315 * @return array The user object and the role ID
2316 */
2317 protected function get_user_objects(testing_data_generator $generator, $contextid) {
2318 global $USER;
2319
2320 if (empty($USER->id)) {
2321 $user = $generator->create_user();
2322 $this->setUser($user);
2323 }
2324 $roleid = create_role('Test role', 'testrole', 'Test role description');
2325 if (!is_array($contextid)) {
2326 $contextid = array($contextid);
2327 }
2328 foreach ($contextid as $cid) {
2329 $assignid = role_assign($roleid, $user->id, $cid);
2330 }
2331 return array($user, $roleid);
2332 }
2333
2334 /**
2335 * Test course move after course.
2336 */
5aff38e4 2337 public function test_course_change_sortorder_after_course() {
5dc361e1
SH
2338 global $DB;
2339
2340 $this->resetAfterTest(true);
2341
2342 $generator = $this->getDataGenerator();
2343 $category = $generator->create_category();
2344 $course3 = $generator->create_course(array('category' => $category->id));
2345 $course2 = $generator->create_course(array('category' => $category->id));
2346 $course1 = $generator->create_course(array('category' => $category->id));
2347 $context = $category->get_context();
2348
2349 list($user, $roleid) = $this->get_user_objects($generator, $context->id);
2350 $caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id);
2351
2352 $courses = $category->get_courses();
2353 $this->assertInternalType('array', $courses);
2354 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2355 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2356 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2357
2358 // Test moving down.
5aff38e4 2359 $this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id));
5dc361e1
SH
2360 $courses = $category->get_courses();
2361 $this->assertInternalType('array', $courses);
2362 $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
2363 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2364 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2365
2366 // Test moving up.
5aff38e4 2367 $this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id));
5dc361e1
SH
2368 $courses = $category->get_courses();
2369 $this->assertInternalType('array', $courses);
2370 $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
2371 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2372 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2373
2374 // Test moving to the top.
5aff38e4 2375 $this->assertTrue(course_change_sortorder_after_course($course1->id, 0));
5dc361e1
SH
2376 $courses = $category->get_courses();
2377 $this->assertInternalType('array', $courses);
2378 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2379 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2380 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2381 }
2382
2383 /**
2384 * Tests changing the visibility of a course.
2385 */
2386 public function test_course_change_visibility() {
2387 global $DB;
2388
2389 $this->resetAfterTest(true);
2390
2391 $generator = $this->getDataGenerator();
2392 $category = $generator->create_category();
2393 $course = $generator->create_course(array('category' => $category->id));
2394
2395 $this->assertEquals('1', $course->visible);
2396 $this->assertEquals('1', $course->visibleold);
2397
2398 $this->assertTrue(course_change_visibility($course->id, false));
2399 $course = $DB->get_record('course', array('id' => $course->id));
2400 $this->assertEquals('0', $course->visible);
2401 $this->assertEquals('0', $course->visibleold);
2402
2403 $this->assertTrue(course_change_visibility($course->id, true));
2404 $course = $DB->get_record('course', array('id' => $course->id));
2405 $this->assertEquals('1', $course->visible);
2406 $this->assertEquals('1', $course->visibleold);
2407 }
2408
2409 /**
2410 * Tests moving the course up and down by one.
2411 */
5aff38e4 2412 public function test_course_change_sortorder_by_one() {
5dc361e1
SH
2413 global $DB;
2414
2415 $this->resetAfterTest(true);
2416
2417 $generator = $this->getDataGenerator();
2418 $category = $generator->create_category();
2419 $course3 = $generator->create_course(array('category' => $category->id));
2420 $course2 = $generator->create_course(array('category' => $category->id));
2421 $course1 = $generator->create_course(array('category' => $category->id));
2422
2423 $courses = $category->get_courses();
2424 $this->assertInternalType('array', $courses);
2425 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2426 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2427 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2428
2429 // Test moving down.
2430 $course1 = get_course($course1->id);
5aff38e4 2431 $this->assertTrue(course_change_sortorder_by_one($course1, false));
5dc361e1
SH
2432 $courses = $category->get_courses();
2433 $this->assertInternalType('array', $courses);
2434 $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
2435 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2436 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2437
2438 // Test moving up.
2439 $course1 = get_course($course1->id);
5aff38e4 2440 $this->assertTrue(course_change_sortorder_by_one($course1, true));
5dc361e1
SH
2441 $courses = $category->get_courses();
2442 $this->assertInternalType('array', $courses);
2443 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2444 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2445 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2446
2447 // Test moving the top course up one.
2448 $course1 = get_course($course1->id);
5aff38e4 2449 $this->assertFalse(course_change_sortorder_by_one($course1, true));
5dc361e1
SH
2450 // Check nothing changed.
2451 $courses = $category->get_courses();
2452 $this->assertInternalType('array', $courses);
2453 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2454 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2455 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2456
2457 // Test moving the bottom course up down.
2458 $course3 = get_course($course3->id);
5aff38e4 2459 $this->assertFalse(course_change_sortorder_by_one($course3, false));
5dc361e1
SH
2460 // Check nothing changed.
2461 $courses = $category->get_courses();
2462 $this->assertInternalType('array', $courses);
2463 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2464 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2465 $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2466 }
957944dc
MG
2467
2468 public function test_view_resources_list() {
2469 $this->resetAfterTest();
2470
2471 $course = self::getDataGenerator()->create_course();
2472 $coursecontext = context_course::instance($course->id);
2473
2474 $event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id)));
2475 $event->set_legacy_logdata(array('book', 'page', 'resource'));
2476 $sink = $this->redirectEvents();
2477 $event->trigger();
2478 $events = $sink->get_events();
2479 $sink->close();
2480
2481 // Validate the event.
2482 $event = $events[0];
2483 $this->assertInstanceOf('\core\event\course_resources_list_viewed', $event);
2484 $this->assertEquals(null, $event->objecttable);
2485 $this->assertEquals(null, $event->objectid);
2486 $this->assertEquals($course->id, $event->courseid);
2487 $this->assertEquals($coursecontext->id, $event->contextid);
957944dc
MG
2488 $expectedlegacydata = array(
2489 array($course->id, "book", "view all", 'index.php?id=' . $course->id, ''),
2490 array($course->id, "page", "view all", 'index.php?id=' . $course->id, ''),
2491 array($course->id, "resource", "view all", 'index.php?id=' . $course->id, ''),
2492 );
2493 $this->assertEventLegacyLogData($expectedlegacydata, $event);
2494 $this->assertEventContextNotUsed($event);
2495 }
0a3d81c2
AA
2496
2497 /**
2498 * Test duplicate_module()
2499 */
2500 public function test_duplicate_module() {
2501 $this->setAdminUser();
2502 $this->resetAfterTest();
2503 $course = self::getDataGenerator()->create_course();
2504 $res = self::getDataGenerator()->create_module('resource', array('course' => $course));
2505 $cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST);
2506
2507 $newcm = duplicate_module($course, $cm);
2508
2509 // Make sure they are the same, except obvious id changes.
2510 foreach ($cm as $prop => $value) {
2511 if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') {
2512 // Ignore obviously different properties.
2513 continue;
2514 }
2515 $this->assertEquals($value, $newcm->$prop);
2516 }
2517 }
06c06038 2518
2519 /**
2520 * Tests that when creating or updating a module, if the availability settings
2521 * are present but set to an empty tree, availability is set to null in
2522 * database.
2523 */
2524 public function test_empty_availability_settings() {
2525 global $DB;
2526 $this->setAdminUser();
2527 $this->resetAfterTest();
2528
2529 // Enable availability.
2530 set_config('enableavailability', 1);
2531
2532 // Test add.
2533 $emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
2534 $course = self::getDataGenerator()->create_course();
2535 $label = self::getDataGenerator()->create_module('label', array(
2536 'course' => $course, 'availability' => $emptyavailability));
2537 $this->assertNull($DB->get_field('course_modules', 'availability',
2538 array('id' => $label->cmid)));
2539
2540 // Test update.
2541 $formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
2542 unset($formdata->availability);
2543 $formdata->availabilityconditionsjson = $emptyavailability;
2544 $formdata->modulename = 'label';
2545 $formdata->coursemodule = $label->cmid;
2546 $draftid = 0;
2547 file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
2548 'mod_label', 'intro', 0);
2549 $formdata->introeditor = array(
2550 'itemid' => $draftid,
2551 'text' => '<p>Yo</p>',
2552 'format' => FORMAT_HTML);
2553 update_module($formdata);
2554 $this->assertNull($DB->get_field('course_modules', 'availability',
2555 array('id' => $label->cmid)));
2556 }
354b214c 2557}