MDL-67673 phpunit: Remove deprecated assertContains() uses on strings
[moodle.git] / course / tests / courselib_test.php
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/>.
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  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->dirroot . '/course/lib.php');
30 require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
31 require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
33 class core_course_courselib_testcase extends advanced_testcase {
35     /**
36      * Set forum specific test values for calling create_module().
37      *
38      * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
39      */
40     private function forum_create_set_values(&$moduleinfo) {
41         // Completion specific to forum - optional.
42         $moduleinfo->completionposts = 3;
43         $moduleinfo->completiondiscussions = 1;
44         $moduleinfo->completionreplies = 2;
46         // Specific values to the Forum module.
47         $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
48         $moduleinfo->type = 'single';
49         $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
50         $moduleinfo->maxbytes = 10240;
51         $moduleinfo->maxattachments = 2;
53         // Post threshold for blocking - specific to forum.
54         $moduleinfo->blockperiod = 60*60*24;
55         $moduleinfo->blockafter = 10;
56         $moduleinfo->warnafter = 5;
58         // Grading of whole forum settings.
59         $moduleinfo->grade_forum = 0;
60     }
62     /**
63      * Execute test asserts on the saved DB data by create_module($forum).
64      *
65      * @param object $moduleinfo - the specific forum values that were used to create a forum.
66      * @param object $dbmodinstance - the DB values of the created forum.
67      */
68     private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
69         // Compare values specific to forums.
70         $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
71         $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
72         $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
73         $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
74         $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
75         $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
76         $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
77         $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
78         $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
79         $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
80         $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
81         $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
82         $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
83         $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
84         $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
85         $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
86         $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
87     }
89     /**
90      * Set assign module specific test values for calling create_module().
91      *
92      * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
93      */
94     private function assign_create_set_values(&$moduleinfo) {
95         // Specific values to the Assign module.
96         $moduleinfo->alwaysshowdescription = true;
97         $moduleinfo->submissiondrafts = true;
98         $moduleinfo->requiresubmissionstatement = true;
99         $moduleinfo->sendnotifications = true;
100         $moduleinfo->sendlatenotifications = true;
101         $moduleinfo->duedate = time() + (7 * 24 * 3600);
102         $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
103         $moduleinfo->gradingduedate = time() + (7 * 24 * 3600);
104         $moduleinfo->allowsubmissionsfromdate = time();
105         $moduleinfo->teamsubmission = true;
106         $moduleinfo->requireallteammemberssubmit = true;
107         $moduleinfo->teamsubmissiongroupingid = true;
108         $moduleinfo->blindmarking = true;
109         $moduleinfo->markingworkflow = true;
110         $moduleinfo->markingallocation = true;
111         $moduleinfo->assignsubmission_onlinetext_enabled = true;
112         $moduleinfo->assignsubmission_file_enabled = true;
113         $moduleinfo->assignsubmission_file_maxfiles = 1;
114         $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
115         $moduleinfo->assignsubmission_comments_enabled = true;
116         $moduleinfo->assignfeedback_comments_enabled = true;
117         $moduleinfo->assignfeedback_offline_enabled = true;
118         $moduleinfo->assignfeedback_file_enabled = true;
120         // Advanced grading.
121         $gradingmethods = grading_manager::available_methods();
122         $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
123     }
125     /**
126      * Execute test asserts on the saved DB data by create_module($assign).
127      *
128      * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
129      * @param object $dbmodinstance - the DB values of the created assign module.
130      */
131     private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
132         global $DB;
134         $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
135         $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
136         $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
137         $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
138         $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
139         $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
140         $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
141         $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
142         $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
143         $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
144         $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
145         $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
146         $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
147         // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
149         // Advanced grading.
150         $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
151         $contextmodule = context_module::instance($cm->id);
152         $advancedgradingmethod = $DB->get_record('grading_areas',
153             array('contextid' => $contextmodule->id,
154                 'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
155         $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
156     }
158     /**
159      * Run some asserts test for a specific module for the function create_module().
160      *
161      * The function has been created (and is called) for $this->test_create_module().
162      * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
163      * So if you want, you can overwrite the default values/asserts in the respective functions.
164      * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
165      */
166     private function create_specific_module_test($modulename) {
167         global $DB, $CFG;
169         $this->resetAfterTest(true);
171         $this->setAdminUser();
173         // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
174         require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
176         // Enable avaibility.
177         // If not enabled all conditional fields will be ignored.
178         set_config('enableavailability', 1);
180         // Enable course completion.
181         // If not enabled all completion settings will be ignored.
182         set_config('enablecompletion', COMPLETION_ENABLED);
184         // Enable forum RSS feeds.
185         set_config('enablerssfeeds', 1);
186         set_config('forum_enablerssfeeds', 1);
188         $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
189            array('createsections'=>true));
191         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
193         // Create assign module instance for test.
194         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
195         $params['course'] = $course->id;
196         $instance = $generator->create_instance($params);
197         $assigncm = get_coursemodule_from_instance('assign', $instance->id);
199         // Module test values.
200         $moduleinfo = new stdClass();
202         // Always mandatory generic values to any module.
203         $moduleinfo->modulename = $modulename;
204         $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
205         $moduleinfo->course = $course->id;
206         $moduleinfo->groupingid = $grouping->id;
207         $moduleinfo->visible = true;
208         $moduleinfo->visibleoncoursepage = true;
210         // Sometimes optional generic values for some modules.
211         $moduleinfo->name = 'My test module';
212         $moduleinfo->showdescription = 1; // standard boolean
213         require_once($CFG->libdir . '/gradelib.php');
214         $gradecats = grade_get_categories_menu($moduleinfo->course, false);
215         $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
216         $moduleinfo->gradecat = $gradecatid;
217         $moduleinfo->groupmode = VISIBLEGROUPS;
218         $moduleinfo->cmidnumber = 'idnumber_XXX';
220         // Completion common to all module.
221         $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
222         $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
223         $moduleinfo->completiongradeitemnumber = 1;
224         $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
226         // Conditional activity.
227         $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
228                 '{"type":"date","d":">=","t":' . time() . '},' .
229                 '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
230                 ']}';
231         $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
232         $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
233         $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
234         $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
236         // Grading and Advanced grading.
237         require_once($CFG->dirroot . '/rating/lib.php');
238         $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
239         $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
240         $moduleinfo->assesstimestart = time();
241         $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
243         // RSS.
244         $moduleinfo->rsstype = 2;
245         $moduleinfo->rssarticles = 10;
247         // Optional intro editor (depends of module).
248         $draftid_editor = 0;
249         file_prepare_draft_area($draftid_editor, null, null, null, null);
250         $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
252         // Following is the advanced grading method area called 'submissions' for the 'assign' module.
253         if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
254             $moduleinfo->grade = 100;
255         }
257         // Plagiarism form values.
258         // No plagiarism plugin installed by default. Use this space to make your own test.
260         // Values specific to the module.
261         $modulesetvalues = $modulename.'_create_set_values';
262         $this->$modulesetvalues($moduleinfo);
264         // Create the module.
265         $result = create_module($moduleinfo);
267         // Retrieve the module info.
268         $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
269         $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
270         // We passed the course section number to create_courses but $dbcm contain the section id.
271         // We need to retrieve the db course section number.
272         $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
273         // Retrieve the grade item.
274         $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
275             'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
277         // Compare the values common to all module instances.
278         $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
279         $this->assertEquals($moduleinfo->section, $section->section);
280         $this->assertEquals($moduleinfo->course, $dbcm->course);
281         $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
282         $this->assertEquals($moduleinfo->visible, $dbcm->visible);
283         $this->assertEquals($moduleinfo->completion, $dbcm->completion);
284         $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
285         $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
286         $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
287         $this->assertEquals($moduleinfo->availability, $dbcm->availability);
288         $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
289         $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
290         $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
291         $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
293         // Optional grade testing.
294         if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
295             $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
296         }
298         // Some optional (but quite common) to some module.
299         $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
300         $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
301         $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
303         // Test specific to the module.
304         $modulerunasserts = $modulename.'_create_run_asserts';
305         $this->$modulerunasserts($moduleinfo, $dbmodinstance);
306         return $moduleinfo;
307     }
309     /**
310      * Create module associated blog and tags.
311      *
312      * @param object $course Course.
313      * @param object $modulecontext The context of the module.
314      */
315     private function create_module_asscociated_blog($course, $modulecontext) {
316         global $DB, $CFG;
318         // Create default group.
319         $group = new stdClass();
320         $group->courseid = $course->id;
321         $group->name = 'Group';
322         $group->id = $DB->insert_record('groups', $group);
324         // Create default user.
325         $user = $this->getDataGenerator()->create_user(array(
326             'username' => 'testuser',
327             'firstname' => 'Firsname',
328             'lastname' => 'Lastname'
329         ));
331         // Create default post.
332         $post = new stdClass();
333         $post->userid = $user->id;
334         $post->groupid = $group->id;
335         $post->content = 'test post content text';
336         $post->module = 'blog';
337         $post->id = $DB->insert_record('post', $post);
339         // Create default tag.
340         $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
341             'rawname' => 'Testtagname', 'isstandard' => 1));
342         // Apply the tag to the blog.
343         $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
344             'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
346         require_once($CFG->dirroot . '/blog/locallib.php');
347         $blog = new blog_entry($post->id);
348         $blog->add_association($modulecontext->id);
350         return $blog;
351     }
353     /**
354      * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
355      */
356     public function test_create_module() {
357         // Add the module name you want to test here.
358         // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
359         $modules = array('forum', 'assign');
360         // Run all tests.
361         foreach ($modules as $modulename) {
362             $this->create_specific_module_test($modulename);
363         }
364     }
366     /**
367      * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
368      */
369     public function test_update_module() {
370         // Add the module name you want to test here.
371         // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
372         $modules = array('forum');
373         // Run all tests.
374         foreach ($modules as $modulename) {
375             $this->update_specific_module_test($modulename);
376         }
377     }
379     /**
380      * Set forum specific test values for calling update_module().
381      *
382      * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
383      */
384     private function forum_update_set_values(&$moduleinfo) {
385         // Completion specific to forum - optional.
386         $moduleinfo->completionposts = 3;
387         $moduleinfo->completiondiscussions = 1;
388         $moduleinfo->completionreplies = 2;
390         // Specific values to the Forum module.
391         $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
392         $moduleinfo->type = 'single';
393         $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
394         $moduleinfo->maxbytes = 10240;
395         $moduleinfo->maxattachments = 2;
397         // Post threshold for blocking - specific to forum.
398         $moduleinfo->blockperiod = 60*60*24;
399         $moduleinfo->blockafter = 10;
400         $moduleinfo->warnafter = 5;
402         // Grading of whole forum settings.
403         $moduleinfo->grade_forum = 0;
404     }
406     /**
407      * Execute test asserts on the saved DB data by update_module($forum).
408      *
409      * @param object $moduleinfo - the specific forum values that were used to update a forum.
410      * @param object $dbmodinstance - the DB values of the updated forum.
411      */
412     private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
413         // Compare values specific to forums.
414         $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
415         $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
416         $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
417         $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
418         $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
419         $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
420         $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
421         $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
422         $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
423         $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
424         $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
425         $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
426         $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
427         $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
428         $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
429         $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
430         $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
431     }
435     /**
436      * Test a specific type of module.
437      *
438      * @param string $modulename - the module name to test
439      */
440     private function update_specific_module_test($modulename) {
441         global $DB, $CFG;
443         $this->resetAfterTest(true);
445         $this->setAdminUser();
447         // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
448         require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
450         // Enable avaibility.
451         // If not enabled all conditional fields will be ignored.
452         set_config('enableavailability', 1);
454         // Enable course completion.
455         // If not enabled all completion settings will be ignored.
456         set_config('enablecompletion', COMPLETION_ENABLED);
458         // Enable forum RSS feeds.
459         set_config('enablerssfeeds', 1);
460         set_config('forum_enablerssfeeds', 1);
462         $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
463            array('createsections'=>true));
465         $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
467         // Create assign module instance for testing gradeitem.
468         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
469         $params['course'] = $course->id;
470         $instance = $generator->create_instance($params);
471         $assigncm = get_coursemodule_from_instance('assign', $instance->id);
473         // Create the test forum to update.
474         $initvalues = new stdClass();
475         $initvalues->introformat = FORMAT_HTML;
476         $initvalues->course = $course->id;
477         $forum = self::getDataGenerator()->create_module('forum', $initvalues);
479         // Retrieve course module.
480         $cm = get_coursemodule_from_instance('forum', $forum->id);
482         // Module test values.
483         $moduleinfo = new stdClass();
485         // Always mandatory generic values to any module.
486         $moduleinfo->coursemodule = $cm->id;
487         $moduleinfo->modulename = $modulename;
488         $moduleinfo->course = $course->id;
489         $moduleinfo->groupingid = $grouping->id;
490         $moduleinfo->visible = true;
491         $moduleinfo->visibleoncoursepage = true;
493         // Sometimes optional generic values for some modules.
494         $moduleinfo->name = 'My test module';
495         $moduleinfo->showdescription = 1; // standard boolean
496         require_once($CFG->libdir . '/gradelib.php');
497         $gradecats = grade_get_categories_menu($moduleinfo->course, false);
498         $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
499         $moduleinfo->gradecat = $gradecatid;
500         $moduleinfo->groupmode = VISIBLEGROUPS;
501         $moduleinfo->cmidnumber = 'idnumber_XXX';
503         // Completion common to all module.
504         $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
505         $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
506         $moduleinfo->completiongradeitemnumber = 1;
507         $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
508         $moduleinfo->completionunlocked = 1;
510         // Conditional activity.
511         $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
512         $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
513                 array(\availability_date\condition::get_json('>=', time()),
514                 \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
515                 \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
516                 \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
517                 \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
519         // Grading and Advanced grading.
520         require_once($CFG->dirroot . '/rating/lib.php');
521         $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
522         $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
523         $moduleinfo->assesstimestart = time();
524         $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
526         // RSS.
527         $moduleinfo->rsstype = 2;
528         $moduleinfo->rssarticles = 10;
530         // Optional intro editor (depends of module).
531         $draftid_editor = 0;
532         file_prepare_draft_area($draftid_editor, null, null, null, null);
533         $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
535         // Following is the advanced grading method area called 'submissions' for the 'assign' module.
536         if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
537             $moduleinfo->grade = 100;
538         }
539         // Plagiarism form values.
540         // No plagiarism plugin installed by default. Use this space to make your own test.
542         // Values specific to the module.
543         $modulesetvalues = $modulename.'_update_set_values';
544         $this->$modulesetvalues($moduleinfo);
546         // Create the module.
547         $result = update_module($moduleinfo);
549         // Retrieve the module info.
550         $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
551         $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
552         // Retrieve the grade item.
553         $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
554             'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
556         // Compare the values common to all module instances.
557         $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
558         $this->assertEquals($moduleinfo->course, $dbcm->course);
559         $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
560         $this->assertEquals($moduleinfo->visible, $dbcm->visible);
561         $this->assertEquals($moduleinfo->completion, $dbcm->completion);
562         $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
563         $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
564         $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
565         $this->assertEquals($moduleinfo->availability, $dbcm->availability);
566         $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
567         $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
568         $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
569         $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
571         // Optional grade testing.
572         if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
573             $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
574         }
576         // Some optional (but quite common) to some module.
577         $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
578         $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
579         $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
581         // Test specific to the module.
582         $modulerunasserts = $modulename.'_update_run_asserts';
583         $this->$modulerunasserts($moduleinfo, $dbmodinstance);
584         return $moduleinfo;
585    }
587     /**
588      * Data provider for course_delete module
589      *
590      * @return array An array of arrays contain test data
591      */
592     public function provider_course_delete_module() {
593         $data = array();
595         $data['assign'] = array('assign', array('duedate' => time()));
596         $data['quiz'] = array('quiz', array('duedate' => time()));
598         return $data;
599     }
601     /**
602      * Test the create_course function
603      */
604     public function test_create_course() {
605         global $DB;
606         $this->resetAfterTest(true);
607         $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
609         $course = new stdClass();
610         $course->fullname = 'Apu loves Unit Təsts';
611         $course->shortname = 'Spread the lŭve';
612         $course->idnumber = '123';
613         $course->summary = 'Awesome!';
614         $course->summaryformat = FORMAT_PLAIN;
615         $course->format = 'topics';
616         $course->newsitems = 0;
617         $course->category = $defaultcategory;
618         $original = (array) $course;
620         $created = create_course($course);
621         $context = context_course::instance($created->id);
623         // Compare original and created.
624         $this->assertEquals($original, array_intersect_key((array) $created, $original));
626         // Ensure default section is created.
627         $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
628         $this->assertTrue($sectioncreated);
630         // Ensure that the shortname isn't duplicated.
631         try {
632             $created = create_course($course);
633             $this->fail('Exception expected');
634         } catch (moodle_exception $e) {
635             $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
636         }
638         // Ensure that the idnumber isn't duplicated.
639         $course->shortname .= '1';
640         try {
641             $created = create_course($course);
642             $this->fail('Exception expected');
643         } catch (moodle_exception $e) {
644             $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
645         }
646     }
648     public function test_create_course_with_generator() {
649         global $DB;
650         $this->resetAfterTest(true);
651         $course = $this->getDataGenerator()->create_course();
653         // Ensure default section is created.
654         $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
655         $this->assertTrue($sectioncreated);
656     }
658     public function test_create_course_sections() {
659         global $DB;
660         $this->resetAfterTest(true);
662         $numsections = 5;
663         $course = $this->getDataGenerator()->create_course(
664                 array('shortname' => 'GrowingCourse',
665                     'fullname' => 'Growing Course',
666                     'numsections' => $numsections),
667                 array('createsections' => true));
669         // Ensure all 6 (0-5) sections were created and course content cache works properly
670         $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
671         $this->assertEquals(range(0, $numsections), $sectionscreated);
673         // this will do nothing, section already exists
674         $this->assertFalse(course_create_sections_if_missing($course, $numsections));
676         // this will create new section
677         $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1));
679         // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
680         $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
681         $this->assertEquals(range(0, $numsections + 1), $sectionscreated);
682     }
684     public function test_update_course() {
685         global $DB;
687         $this->resetAfterTest();
689         $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
691         $course = new stdClass();
692         $course->fullname = 'Apu loves Unit Təsts';
693         $course->shortname = 'test1';
694         $course->idnumber = '1';
695         $course->summary = 'Awesome!';
696         $course->summaryformat = FORMAT_PLAIN;
697         $course->format = 'topics';
698         $course->newsitems = 0;
699         $course->numsections = 5;
700         $course->category = $defaultcategory;
702         $created = create_course($course);
703         // Ensure the checks only work on idnumber/shortname that are not already ours.
704         update_course($created);
706         $course->shortname = 'test2';
707         $course->idnumber = '2';
709         $created2 = create_course($course);
711         // Test duplicate idnumber.
712         $created2->idnumber = '1';
713         try {
714             update_course($created2);
715             $this->fail('Expected exception when trying to update a course with duplicate idnumber');
716         } catch (moodle_exception $e) {
717             $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
718         }
720         // Test duplicate shortname.
721         $created2->idnumber = '2';
722         $created2->shortname = 'test1';
723         try {
724             update_course($created2);
725             $this->fail('Expected exception when trying to update a course with a duplicate shortname');
726         } catch (moodle_exception $e) {
727             $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
728         }
729     }
731     public function test_update_course_section_time_modified() {
732         global $DB;
734         $this->resetAfterTest();
736         // Create the course with sections.
737         $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
738         $sections = $DB->get_records('course_sections', array('course' => $course->id));
740         // Get the last section's time modified value.
741         $section = array_pop($sections);
742         $oldtimemodified = $section->timemodified;
744         // Update the section.
745         $this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp.
746         course_update_section($course, $section, array());
748         // Check that the time has changed.
749         $section = $DB->get_record('course_sections', array('id' => $section->id));
750         $newtimemodified = $section->timemodified;
751         $this->assertGreaterThan($oldtimemodified, $newtimemodified);
752     }
754     /**
755      * Relative dates mode settings provider for course creation.
756      */
757     public function create_course_relative_dates_provider() {
758         return [
759             [0, 0, 0],
760             [0, 1, 0],
761             [1, 0, 0],
762             [1, 1, 1],
763         ];
764     }
766     /**
767      * Test create_course by attempting to change the relative dates mode.
768      *
769      * @dataProvider create_course_relative_dates_provider
770      * @param int $setting The value for the 'enablecourserelativedates' admin setting.
771      * @param int $mode The value for the course's 'relativedatesmode' field.
772      * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation.
773      */
774     public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) {
775         global $DB;
777         $this->resetAfterTest();
779         set_config('enablecourserelativedates', $setting);
781         // Generate a course with relative dates mode set to $mode.
782         $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]);
784         // Verify that the relative dates match what's expected.
785         $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
786         $this->assertEquals($expectedvalue, $relativedatesmode);
787     }
789     /**
790      * Test update_course by attempting to change the relative dates mode.
791      */
792     public function test_relative_dates_mode_for_course_update() {
793         global $DB;
795         $this->resetAfterTest();
797         set_config('enablecourserelativedates', 1);
799         // Generate a course with relative dates mode set to 1.
800         $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]);
802         // Attempt to update the course with a changed relativedatesmode.
803         $course->relativedatesmode = 0;
804         update_course($course);
806         // Verify that the relative dates mode has not changed.
807         $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
808         $this->assertEquals(1, $relativedatesmode);
809     }
811     public function test_course_add_cm_to_section() {
812         global $DB;
813         $this->resetAfterTest(true);
815         // Create course with 1 section.
816         $course = $this->getDataGenerator()->create_course(
817                 array('shortname' => 'GrowingCourse',
818                     'fullname' => 'Growing Course',
819                     'numsections' => 1),
820                 array('createsections' => true));
822         // Trash modinfo.
823         rebuild_course_cache($course->id, true);
825         // Create some cms for testing.
826         $cmids = array();
827         for ($i=0; $i<4; $i++) {
828             $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
829         }
831         // Add it to section that exists.
832         course_add_cm_to_section($course, $cmids[0], 1);
834         // Check it got added to sequence.
835         $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
836         $this->assertEquals($cmids[0], $sequence);
838         // Add a second, this time using courseid variant of parameters.
839         $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
840         course_add_cm_to_section($course->id, $cmids[1], 1);
841         $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
842         $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
844         // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
845         $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
846         $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
848         // Add one to section that doesn't exist (this might rebuild modinfo).
849         course_add_cm_to_section($course, $cmids[2], 2);
850         $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
851         $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
852         $this->assertEquals($cmids[2], $sequence);
854         // Add using the 'before' option.
855         course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
856         $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
857         $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
858         $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
859     }
861     public function test_reorder_sections() {
862         global $DB;
863         $this->resetAfterTest(true);
865         $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
866         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
867         $oldsections = array();
868         $sections = array();
869         foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
870             $oldsections[$section->section] = $section->id;
871             $sections[$section->id] = $section->section;
872         }
873         ksort($oldsections);
875         $neworder = reorder_sections($sections, 2, 4);
876         $neworder = array_keys($neworder);
877         $this->assertEquals($oldsections[0], $neworder[0]);
878         $this->assertEquals($oldsections[1], $neworder[1]);
879         $this->assertEquals($oldsections[2], $neworder[4]);
880         $this->assertEquals($oldsections[3], $neworder[2]);
881         $this->assertEquals($oldsections[4], $neworder[3]);
882         $this->assertEquals($oldsections[5], $neworder[5]);
883         $this->assertEquals($oldsections[6], $neworder[6]);
885         $neworder = reorder_sections($sections, 4, 2);
886         $neworder = array_keys($neworder);
887         $this->assertEquals($oldsections[0], $neworder[0]);
888         $this->assertEquals($oldsections[1], $neworder[1]);
889         $this->assertEquals($oldsections[2], $neworder[3]);
890         $this->assertEquals($oldsections[3], $neworder[4]);
891         $this->assertEquals($oldsections[4], $neworder[2]);
892         $this->assertEquals($oldsections[5], $neworder[5]);
893         $this->assertEquals($oldsections[6], $neworder[6]);
895         $neworder = reorder_sections(1, 2, 4);
896         $this->assertFalse($neworder);
897     }
899     public function test_move_section_down() {
900         global $DB;
901         $this->resetAfterTest(true);
903         $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
904         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
905         $oldsections = array();
906         foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
907             $oldsections[$section->section] = $section->id;
908         }
909         ksort($oldsections);
911         // Test move section down..
912         move_section_to($course, 2, 4);
913         $sections = array();
914         foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
915             $sections[$section->section] = $section->id;
916         }
917         ksort($sections);
919         $this->assertEquals($oldsections[0], $sections[0]);
920         $this->assertEquals($oldsections[1], $sections[1]);
921         $this->assertEquals($oldsections[2], $sections[4]);
922         $this->assertEquals($oldsections[3], $sections[2]);
923         $this->assertEquals($oldsections[4], $sections[3]);
924         $this->assertEquals($oldsections[5], $sections[5]);
925         $this->assertEquals($oldsections[6], $sections[6]);
926     }
928     public function test_move_section_up() {
929         global $DB;
930         $this->resetAfterTest(true);
932         $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
933         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
934         $oldsections = array();
935         foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
936             $oldsections[$section->section] = $section->id;
937         }
938         ksort($oldsections);
940         // Test move section up..
941         move_section_to($course, 6, 4);
942         $sections = array();
943         foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
944             $sections[$section->section] = $section->id;
945         }
946         ksort($sections);
948         $this->assertEquals($oldsections[0], $sections[0]);
949         $this->assertEquals($oldsections[1], $sections[1]);
950         $this->assertEquals($oldsections[2], $sections[2]);
951         $this->assertEquals($oldsections[3], $sections[3]);
952         $this->assertEquals($oldsections[4], $sections[5]);
953         $this->assertEquals($oldsections[5], $sections[6]);
954         $this->assertEquals($oldsections[6], $sections[4]);
955     }
957     public function test_move_section_marker() {
958         global $DB;
959         $this->resetAfterTest(true);
961         $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
962         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
964         // Set course marker to the section we are going to move..
965         course_set_marker($course->id, 2);
966         // Verify that the course marker is set correctly.
967         $course = $DB->get_record('course', array('id' => $course->id));
968         $this->assertEquals(2, $course->marker);
970         // Test move the marked section down..
971         move_section_to($course, 2, 4);
973         // Verify that the course marker has been moved along with the section..
974         $course = $DB->get_record('course', array('id' => $course->id));
975         $this->assertEquals(4, $course->marker);
977         // Test move the marked section up..
978         move_section_to($course, 4, 3);
980         // Verify that the course marker has been moved along with the section..
981         $course = $DB->get_record('course', array('id' => $course->id));
982         $this->assertEquals(3, $course->marker);
984         // Test moving a non-marked section above the marked section..
985         move_section_to($course, 4, 2);
987         // Verify that the course marker has been moved down to accomodate..
988         $course = $DB->get_record('course', array('id' => $course->id));
989         $this->assertEquals(4, $course->marker);
991         // Test moving a non-marked section below the marked section..
992         move_section_to($course, 3, 6);
994         // Verify that the course marker has been up to accomodate..
995         $course = $DB->get_record('course', array('id' => $course->id));
996         $this->assertEquals(3, $course->marker);
997     }
999     public function test_course_can_delete_section() {
1000         global $DB;
1001         $this->resetAfterTest(true);
1003         $generator = $this->getDataGenerator();
1005         $courseweeks = $generator->create_course(
1006             array('numsections' => 5, 'format' => 'weeks'),
1007             array('createsections' => true));
1008         $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
1009         $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
1011         $coursetopics = $generator->create_course(
1012             array('numsections' => 5, 'format' => 'topics'),
1013             array('createsections' => true));
1015         $coursesingleactivity = $generator->create_course(
1016             array('format' => 'singleactivity'),
1017             array('createsections' => true));
1019         // Enrol student and teacher.
1020         $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
1021         $student = $generator->create_user();
1022         $teacher = $generator->create_user();
1024         $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
1025         $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
1027         $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
1028         $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
1030         $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
1031         $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
1033         // Teacher should be able to delete sections (except for 0) in topics and weeks format.
1034         $this->setUser($teacher);
1036         // For topics and weeks formats will return false for section 0 and true for any other section.
1037         $this->assertFalse(course_can_delete_section($courseweeks, 0));
1038         $this->assertTrue(course_can_delete_section($courseweeks, 1));
1040         $this->assertFalse(course_can_delete_section($coursetopics, 0));
1041         $this->assertTrue(course_can_delete_section($coursetopics, 1));
1043         // For singleactivity course format no section can be deleted.
1044         $this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
1045         $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
1047         // Now let's revoke a capability from teacher to manage activity in section 1.
1048         $modulecontext = context_module::instance($assign1->cmid);
1049         assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
1050             $modulecontext);
1051         $this->assertFalse(course_can_delete_section($courseweeks, 1));
1052         $this->assertTrue(course_can_delete_section($courseweeks, 2));
1054         // Student does not have permissions to delete sections.
1055         $this->setUser($student);
1056         $this->assertFalse(course_can_delete_section($courseweeks, 1));
1057         $this->assertFalse(course_can_delete_section($coursetopics, 1));
1058         $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
1059     }
1061     public function test_course_delete_section() {
1062         global $DB;
1063         $this->resetAfterTest(true);
1065         $generator = $this->getDataGenerator();
1067         $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
1068             array('createsections' => true));
1069         $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
1070         $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
1071         $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
1072         $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
1073         $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
1074         $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
1075         $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
1077         $this->setAdminUser();
1079         // Attempt to delete non-existing section.
1080         $this->assertFalse(course_delete_section($course, 10, false));
1081         $this->assertFalse(course_delete_section($course, 9, true));
1083         // Attempt to delete 0-section.
1084         $this->assertFalse(course_delete_section($course, 0, true));
1085         $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
1087         // Delete last section.
1088         $this->assertTrue(course_delete_section($course, 6, true));
1089         $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
1090         $this->assertEquals(5, course_get_format($course)->get_last_section_number());
1092         // Delete empty section.
1093         $this->assertTrue(course_delete_section($course, 4, false));
1094         $this->assertEquals(4, course_get_format($course)->get_last_section_number());
1096         // Delete section in the middle (2).
1097         $this->assertFalse(course_delete_section($course, 2, false));
1098         $this->assertTrue(course_delete_section($course, 2, true));
1099         $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
1100         $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
1101         $this->assertEquals(3, course_get_format($course)->get_last_section_number());
1102         $this->assertEquals(array(0 => array($assign0->cmid),
1103             1 => array($assign1->cmid),
1104             2 => array($assign3->cmid),
1105             3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
1107         // Remove marked section.
1108         course_set_marker($course->id, 1);
1109         $this->assertTrue(course_get_format($course)->is_section_current(1));
1110         $this->assertTrue(course_delete_section($course, 1, true));
1111         $this->assertFalse(course_get_format($course)->is_section_current(1));
1112     }
1114     public function test_get_course_display_name_for_list() {
1115         global $CFG;
1116         $this->resetAfterTest(true);
1118         $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
1120         $CFG->courselistshortnames = 0;
1121         $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
1123         $CFG->courselistshortnames = 1;
1124         $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
1125     }
1127     public function test_move_module_in_course() {
1128         global $DB;
1130         $this->resetAfterTest(true);
1131         // Setup fixture
1132         $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
1133         $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
1135         $cms = get_fast_modinfo($course)->get_cms();
1136         $cm = reset($cms);
1138         $newsection = get_fast_modinfo($course)->get_section_info(3);
1139         $oldsectionid = $cm->section;
1141         // Perform the move
1142         moveto_module($cm, $newsection);
1144         $cms = get_fast_modinfo($course)->get_cms();
1145         $cm = reset($cms);
1147         // Check that the cached modinfo contains the correct section info
1148         $modinfo = get_fast_modinfo($course);
1149         $this->assertTrue(empty($modinfo->sections[0]));
1150         $this->assertFalse(empty($modinfo->sections[3]));
1152         // Check that the old section's sequence no longer contains this ID
1153         $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
1154         $oldsequences = explode(',', $newsection->sequence);
1155         $this->assertFalse(in_array($cm->id, $oldsequences));
1157         // Check that the new section's sequence now contains this ID
1158         $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
1159         $newsequences = explode(',', $newsection->sequence);
1160         $this->assertTrue(in_array($cm->id, $newsequences));
1162         // Check that the section number has been changed in the cm
1163         $this->assertEquals($newsection->id, $cm->section);
1166         // Perform a second move as some issues were only seen on the second move
1167         $newsection = get_fast_modinfo($course)->get_section_info(2);
1168         $oldsectionid = $cm->section;
1169         moveto_module($cm, $newsection);
1171         $cms = get_fast_modinfo($course)->get_cms();
1172         $cm = reset($cms);
1174         // Check that the cached modinfo contains the correct section info
1175         $modinfo = get_fast_modinfo($course);
1176         $this->assertTrue(empty($modinfo->sections[0]));
1177         $this->assertFalse(empty($modinfo->sections[2]));
1179         // Check that the old section's sequence no longer contains this ID
1180         $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
1181         $oldsequences = explode(',', $newsection->sequence);
1182         $this->assertFalse(in_array($cm->id, $oldsequences));
1184         // Check that the new section's sequence now contains this ID
1185         $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
1186         $newsequences = explode(',', $newsection->sequence);
1187         $this->assertTrue(in_array($cm->id, $newsequences));
1188     }
1190     public function test_module_visibility() {
1191         $this->setAdminUser();
1192         $this->resetAfterTest(true);
1194         // Create course and modules.
1195         $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
1196         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1197         $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
1198         $modules = compact('forum', 'assign');
1200         // Hiding the modules.
1201         foreach ($modules as $mod) {
1202             set_coursemodule_visible($mod->cmid, 0);
1203             $this->check_module_visibility($mod, 0, 0);
1204         }
1206         // Showing the modules.
1207         foreach ($modules as $mod) {
1208             set_coursemodule_visible($mod->cmid, 1);
1209             $this->check_module_visibility($mod, 1, 1);
1210         }
1211     }
1213     public function test_section_visibility_events() {
1214         $this->setAdminUser();
1215         $this->resetAfterTest(true);
1217         $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
1218         $sectionnumber = 1;
1219         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1220             array('section' => $sectionnumber));
1221         $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1222             'course' => $course->id), array('section' => $sectionnumber));
1223         $sink = $this->redirectEvents();
1224         set_section_visible($course->id, $sectionnumber, 0);
1225         $events = $sink->get_events();
1227         // Extract the number of events related to what we are testing, other events
1228         // such as course_section_updated could have been triggered.
1229         $count = 0;
1230         foreach ($events as $event) {
1231             if ($event instanceof \core\event\course_module_updated) {
1232                 $count++;
1233             }
1234         }
1235         $this->assertSame(2, $count);
1236         $sink->close();
1237     }
1239     public function test_section_visibility() {
1240         $this->setAdminUser();
1241         $this->resetAfterTest(true);
1243         // Create course.
1244         $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
1246         $sink = $this->redirectEvents();
1248         // Testing an empty section.
1249         $sectionnumber = 1;
1250         set_section_visible($course->id, $sectionnumber, 0);
1251         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1252         $this->assertEquals($section_info->visible, 0);
1253         set_section_visible($course->id, $sectionnumber, 1);
1254         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1255         $this->assertEquals($section_info->visible, 1);
1257         // Checking that an event was fired.
1258         $events = $sink->get_events();
1259         $this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
1260         $sink->close();
1262         // Testing a section with visible modules.
1263         $sectionnumber = 2;
1264         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1265                 array('section' => $sectionnumber));
1266         $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1267                 'course' => $course->id), array('section' => $sectionnumber));
1268         $modules = compact('forum', 'assign');
1269         set_section_visible($course->id, $sectionnumber, 0);
1270         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1271         $this->assertEquals($section_info->visible, 0);
1272         foreach ($modules as $mod) {
1273             $this->check_module_visibility($mod, 0, 1);
1274         }
1275         set_section_visible($course->id, $sectionnumber, 1);
1276         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1277         $this->assertEquals($section_info->visible, 1);
1278         foreach ($modules as $mod) {
1279             $this->check_module_visibility($mod, 1, 1);
1280         }
1282         // Testing a section with hidden modules, which should stay hidden.
1283         $sectionnumber = 3;
1284         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1285                 array('section' => $sectionnumber));
1286         $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1287                 'course' => $course->id), array('section' => $sectionnumber));
1288         $modules = compact('forum', 'assign');
1289         foreach ($modules as $mod) {
1290             set_coursemodule_visible($mod->cmid, 0);
1291             $this->check_module_visibility($mod, 0, 0);
1292         }
1293         set_section_visible($course->id, $sectionnumber, 0);
1294         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1295         $this->assertEquals($section_info->visible, 0);
1296         foreach ($modules as $mod) {
1297             $this->check_module_visibility($mod, 0, 0);
1298         }
1299         set_section_visible($course->id, $sectionnumber, 1);
1300         $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1301         $this->assertEquals($section_info->visible, 1);
1302         foreach ($modules as $mod) {
1303             $this->check_module_visibility($mod, 0, 0);
1304         }
1305     }
1307     /**
1308      * Helper function to assert that a module has correctly been made visible, or hidden.
1309      *
1310      * @param stdClass $mod module information
1311      * @param int $visibility the current state of the module
1312      * @param int $visibleold the current state of the visibleold property
1313      * @return void
1314      */
1315     public function check_module_visibility($mod, $visibility, $visibleold) {
1316         global $DB;
1317         $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
1318         $this->assertEquals($visibility, $cm->visible);
1319         $this->assertEquals($visibleold, $cm->visibleold);
1321         // Check the module grade items.
1322         $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
1323                 'iteminstance' => $cm->instance, 'courseid' => $cm->course));
1324         if ($grade_items) {
1325             foreach ($grade_items as $grade_item) {
1326                 if ($visibility) {
1327                     $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
1328                 } else {
1329                     $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
1330                 }
1331             }
1332         }
1334         // Check the events visibility.
1335         if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
1336             foreach ($events as $event) {
1337                 $calevent = new calendar_event($event);
1338                 $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
1339             }
1340         }
1341     }
1343     public function test_course_page_type_list() {
1344         global $DB;
1345         $this->resetAfterTest(true);
1347         // Create a category.
1348         $category = new stdClass();
1349         $category->name = 'Test Category';
1351         $testcategory = $this->getDataGenerator()->create_category($category);
1353         // Create a course.
1354         $course = new stdClass();
1355         $course->fullname = 'Apu loves Unit Təsts';
1356         $course->shortname = 'Spread the lŭve';
1357         $course->idnumber = '123';
1358         $course->summary = 'Awesome!';
1359         $course->summaryformat = FORMAT_PLAIN;
1360         $course->format = 'topics';
1361         $course->newsitems = 0;
1362         $course->numsections = 5;
1363         $course->category = $testcategory->id;
1365         $testcourse = $this->getDataGenerator()->create_course($course);
1367         // Create contexts.
1368         $coursecontext = context_course::instance($testcourse->id);
1369         $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
1370         $pagetype = 'page-course-x'; // Not used either.
1371         $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
1373         // Page type lists for normal courses.
1374         $testpagetypelist1 = array();
1375         $testpagetypelist1['*'] = 'Any page';
1376         $testpagetypelist1['course-*'] = 'Any course page';
1377         $testpagetypelist1['course-view-*'] = 'Any type of course main page';
1379         $this->assertEquals($testpagetypelist1, $pagetypelist);
1381         // Get the context for the front page course.
1382         $sitecoursecontext = context_course::instance(SITEID);
1383         $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
1385         // Page type list for the front page course.
1386         $testpagetypelist2 = array('*' => 'Any page');
1387         $this->assertEquals($testpagetypelist2, $pagetypelist);
1389         // Make sure that providing no current context to the function doesn't result in an error.
1390         // Calls made from generate_page_type_patterns() may provide null values.
1391         $pagetypelist = course_page_type_list($pagetype, null, null);
1392         $this->assertEquals($pagetypelist, $testpagetypelist1);
1393     }
1395     public function test_compare_activities_by_time_desc() {
1397         // Let's create some test data.
1398         $activitiesivities = array();
1399         $x = new stdClass();
1400         $x->timestamp = null;
1401         $activities[] = $x;
1403         $x = new stdClass();
1404         $x->timestamp = 1;
1405         $activities[] = $x;
1407         $x = new stdClass();
1408         $x->timestamp = 3;
1409         $activities[] = $x;
1411         $x = new stdClass();
1412         $x->timestamp = 0;
1413         $activities[] = $x;
1415         $x = new stdClass();
1416         $x->timestamp = 5;
1417         $activities[] = $x;
1419         $x = new stdClass();
1420         $activities[] = $x;
1422         $x = new stdClass();
1423         $x->timestamp = 5;
1424         $activities[] = $x;
1426         // Do the sorting.
1427         usort($activities, 'compare_activities_by_time_desc');
1429         // Let's check the result.
1430         $last = 10;
1431         foreach($activities as $activity) {
1432             if (empty($activity->timestamp)) {
1433                 $activity->timestamp = 0;
1434             }
1435             $this->assertLessThanOrEqual($last, $activity->timestamp);
1436         }
1437     }
1439     public function test_compare_activities_by_time_asc() {
1441         // Let's create some test data.
1442         $activities = array();
1443         $x = new stdClass();
1444         $x->timestamp = null;
1445         $activities[] = $x;
1447         $x = new stdClass();
1448         $x->timestamp = 1;
1449         $activities[] = $x;
1451         $x = new stdClass();
1452         $x->timestamp = 3;
1453         $activities[] = $x;
1455         $x = new stdClass();
1456         $x->timestamp = 0;
1457         $activities[] = $x;
1459         $x = new stdClass();
1460         $x->timestamp = 5;
1461         $activities[] = $x;
1463         $x = new stdClass();
1464         $activities[] = $x;
1466         $x = new stdClass();
1467         $x->timestamp = 5;
1468         $activities[] = $x;
1470         // Do the sorting.
1471         usort($activities, 'compare_activities_by_time_asc');
1473         // Let's check the result.
1474         $last = 0;
1475         foreach($activities as $activity) {
1476             if (empty($activity->timestamp)) {
1477                 $activity->timestamp = 0;
1478             }
1479             $this->assertGreaterThanOrEqual($last, $activity->timestamp);
1480         }
1481     }
1483     /**
1484      * Tests moving a module between hidden/visible sections and
1485      * verifies that the course/module visiblity seettings are
1486      * retained.
1487      */
1488     public function test_moveto_module_between_hidden_sections() {
1489         global $DB;
1491         $this->resetAfterTest(true);
1493         $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
1494         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1495         $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1496         $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
1498         // Set the page as hidden
1499         set_coursemodule_visible($page->cmid, 0);
1501         // Set sections 3 as hidden.
1502         set_section_visible($course->id, 3, 0);
1504         $modinfo = get_fast_modinfo($course);
1506         $hiddensection = $modinfo->get_section_info(3);
1507         // New section is definitely not visible:
1508         $this->assertEquals($hiddensection->visible, 0);
1510         $forumcm = $modinfo->cms[$forum->cmid];
1511         $pagecm = $modinfo->cms[$page->cmid];
1513         // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
1514         $this->assertEquals(0, moveto_module($forumcm, $hiddensection));
1515         $this->assertEquals(0, moveto_module($pagecm, $hiddensection));
1517         $modinfo = get_fast_modinfo($course);
1519         // Verify that forum and page have been moved to the hidden section and quiz has not.
1520         $this->assertContains($forum->cmid, $modinfo->sections[3]);
1521         $this->assertContains($page->cmid, $modinfo->sections[3]);
1522         $this->assertNotContains($quiz->cmid, $modinfo->sections[3]);
1524         // Verify that forum has been made invisible.
1525         $forumcm = $modinfo->cms[$forum->cmid];
1526         $this->assertEquals($forumcm->visible, 0);
1527         // Verify that old state has been retained.
1528         $this->assertEquals($forumcm->visibleold, 1);
1530         // Verify that page has stayed invisible.
1531         $pagecm = $modinfo->cms[$page->cmid];
1532         $this->assertEquals($pagecm->visible, 0);
1533         // Verify that old state has been retained.
1534         $this->assertEquals($pagecm->visibleold, 0);
1536         // Verify that quiz has been unaffected.
1537         $quizcm = $modinfo->cms[$quiz->cmid];
1538         $this->assertEquals($quizcm->visible, 1);
1540         // Move forum and page back to visible section.
1541         // Make sure the visibility is restored to the original value (visible for forum and hidden for page).
1542         $visiblesection = $modinfo->get_section_info(2);
1543         $this->assertEquals(1, moveto_module($forumcm, $visiblesection));
1544         $this->assertEquals(0, moveto_module($pagecm, $visiblesection));
1546         $modinfo = get_fast_modinfo($course);
1548         // Double check that forum has been made visible.
1549         $forumcm = $modinfo->cms[$forum->cmid];
1550         $this->assertEquals($forumcm->visible, 1);
1552         // Double check that page has stayed invisible.
1553         $pagecm = $modinfo->cms[$page->cmid];
1554         $this->assertEquals($pagecm->visible, 0);
1556         // Move the page in the same section (this is what mod duplicate does).
1557         // Visibility of page remains 0.
1558         $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
1560         // Double check that the the page is still hidden.
1561         $modinfo = get_fast_modinfo($course);
1562         $pagecm = $modinfo->cms[$page->cmid];
1563         $this->assertEquals($pagecm->visible, 0);
1564     }
1566     /**
1567      * Tests moving a module around in the same section. moveto_module()
1568      * is called this way in modduplicate.
1569      */
1570     public function test_moveto_module_in_same_section() {
1571         global $DB;
1573         $this->resetAfterTest(true);
1575         $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
1576         $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1577         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1579         // Simulate inconsistent visible/visibleold values (MDL-38713).
1580         $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
1581         $cm->visible = 0;
1582         $cm->visibleold = 1;
1583         $DB->update_record('course_modules', $cm);
1585         $modinfo = get_fast_modinfo($course);
1586         $forumcm = $modinfo->cms[$forum->cmid];
1587         $pagecm = $modinfo->cms[$page->cmid];
1589         // Verify that page is hidden.
1590         $this->assertEquals($pagecm->visible, 0);
1592         // Verify section 0 is where all mods added.
1593         $section = $modinfo->get_section_info(0);
1594         $this->assertEquals($section->id, $forumcm->section);
1595         $this->assertEquals($section->id, $pagecm->section);
1598         // Move the page inside the hidden section. Make sure it is hidden.
1599         $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
1601         // Double check that the the page is still hidden.
1602         $modinfo = get_fast_modinfo($course);
1603         $pagecm = $modinfo->cms[$page->cmid];
1604         $this->assertEquals($pagecm->visible, 0);
1605     }
1607     /**
1608      * Tests the function that deletes a course module
1609      *
1610      * @param string $type The type of module for the test
1611      * @param array $options The options for the module creation
1612      * @dataProvider provider_course_delete_module
1613      */
1614     public function test_course_delete_module($type, $options) {
1615         global $DB;
1617         $this->resetAfterTest(true);
1618         $this->setAdminUser();
1620         // Create course and modules.
1621         $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
1622         $options['course'] = $course->id;
1624         // Generate an assignment with due date (will generate a course event).
1625         $module = $this->getDataGenerator()->create_module($type, $options);
1627         // Get the module context.
1628         $modcontext = context_module::instance($module->cmid);
1630         $assocblog = $this->create_module_asscociated_blog($course, $modcontext);
1632         // Verify context exists.
1633         $this->assertInstanceOf('context_module', $modcontext);
1635         // Make module specific messes.
1636         switch ($type) {
1637             case 'assign':
1638                 // Add some tags to this assignment.
1639                 core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
1640                 core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
1642                 // Confirm the tag instances were added.
1643                 $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
1644                 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
1645                 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
1646                 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
1648                 // Verify event assignment event has been generated.
1649                 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
1650                 $this->assertEquals(1, $eventcount);
1652                 break;
1653             case 'quiz':
1654                 $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
1655                 $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
1656                 $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
1657                 $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
1658                 break;
1659             default:
1660                 break;
1661         }
1663         // Run delete..
1664         course_delete_module($module->cmid);
1666         // Verify the context has been removed.
1667         $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
1669         // Verify the course_module record has been deleted.
1670         $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
1671         $this->assertEmpty($cmcount);
1673         // Verify the blog_association record has been deleted.
1674         $this->assertCount(0, $DB->get_records('blog_association',
1675                 array('contextid' => $modcontext->id)));
1677         // Verify the blog post record has been deleted.
1678         $this->assertCount(0, $DB->get_records('post',
1679                 array('id' => $assocblog->id)));
1681         // Verify the tag instance record has been deleted.
1682         $this->assertCount(0, $DB->get_records('tag_instance',
1683                 array('itemid' => $assocblog->id)));
1685         // Test clean up of module specific messes.
1686         switch ($type) {
1687             case 'assign':
1688                 // Verify event assignment events have been removed.
1689                 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
1690                 $this->assertEmpty($eventcount);
1692                 // Verify the tag instances were deleted.
1693                 $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
1694                 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
1696                 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
1697                 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
1698                 break;
1699             case 'quiz':
1700                 // Verify category deleted.
1701                 $criteria = array('contextid' => $modcontext->id);
1702                 $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
1704                 // Verify questions deleted.
1705                 $criteria = array('category' => $qcat->id);
1706                 $this->assertEquals(0, $DB->count_records('question', $criteria));
1707                 break;
1708             default:
1709                 break;
1710         }
1711     }
1713     /**
1714      * Test that triggering a course_created event works as expected.
1715      */
1716     public function test_course_created_event() {
1717         global $DB;
1719         $this->resetAfterTest();
1721         // Catch the events.
1722         $sink = $this->redirectEvents();
1724         // Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
1725         $data = new stdClass();
1726         $data->idnumber = 'idnumber';
1727         $course = $this->getDataGenerator()->create_course($data);
1728         // Get course from DB for comparison.
1729         $course = $DB->get_record('course', array('id' => $course->id));
1731         // Capture the event.
1732         $events = $sink->get_events();
1733         $sink->close();
1735         // Validate the event.
1736         $event = $events[0];
1737         $this->assertInstanceOf('\core\event\course_created', $event);
1738         $this->assertEquals('course', $event->objecttable);
1739         $this->assertEquals($course->id, $event->objectid);
1740         $this->assertEquals(context_course::instance($course->id), $event->get_context());
1741         $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1742         $this->assertEquals('course_created', $event->get_legacy_eventname());
1743         $this->assertEventLegacyData($course, $event);
1744         $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
1745         $this->assertEventLegacyLogData($expectedlog, $event);
1747         // Now we want to trigger creating a course via the imsenterprise.
1748         // Delete the course we created earlier, as we want the imsenterprise plugin to create this.
1749         // We do not want print out any of the text this function generates while doing this, which is why
1750         // we are using ob_start() and ob_end_clean().
1751         ob_start();
1752         delete_course($course);
1753         ob_end_clean();
1755         // Create the XML file we want to use.
1756         $course->category = (array)$course->category;
1757         $imstestcase = new enrol_imsenterprise_testcase();
1758         $imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
1759         $imstestcase->set_test_config();
1760         $imstestcase->set_xml_file(false, array($course));
1762         // Capture the event.
1763         $sink = $this->redirectEvents();
1764         $imstestcase->imsplugin->cron();
1765         $events = $sink->get_events();
1766         $sink->close();
1767         $event = null;
1768         foreach ($events as $eventinfo) {
1769             if ($eventinfo instanceof \core\event\course_created ) {
1770                 $event = $eventinfo;
1771                 break;
1772             }
1773         }
1775         // Validate the event triggered is \core\event\course_created. There is no need to validate the other values
1776         // as they have already been validated in the previous steps. Here we only want to make sure that when the
1777         // imsenterprise plugin creates a course an event is triggered.
1778         $this->assertInstanceOf('\core\event\course_created', $event);
1779         $this->assertEventContextNotUsed($event);
1780     }
1782     /**
1783      * Test that triggering a course_updated event works as expected.
1784      */
1785     public function test_course_updated_event() {
1786         global $DB;
1788         $this->resetAfterTest();
1790         // Create a course.
1791         $course = $this->getDataGenerator()->create_course();
1793         // Create a category we are going to move this course to.
1794         $category = $this->getDataGenerator()->create_category();
1796         // Create a hidden category we are going to move this course to.
1797         $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
1799         // Update course and catch course_updated event.
1800         $sink = $this->redirectEvents();
1801         update_course($course);
1802         $events = $sink->get_events();
1803         $sink->close();
1805         // Get updated course information from the DB.
1806         $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1807         // Validate event.
1808         $event = array_shift($events);
1809         $this->assertInstanceOf('\core\event\course_updated', $event);
1810         $this->assertEquals('course', $event->objecttable);
1811         $this->assertEquals($updatedcourse->id, $event->objectid);
1812         $this->assertEquals(context_course::instance($course->id), $event->get_context());
1813         $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
1814         $this->assertEquals($url, $event->get_url());
1815         $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
1816         $this->assertEquals('course_updated', $event->get_legacy_eventname());
1817         $this->assertEventLegacyData($updatedcourse, $event);
1818         $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
1819         $this->assertEventLegacyLogData($expectedlog, $event);
1821         // Move course and catch course_updated event.
1822         $sink = $this->redirectEvents();
1823         move_courses(array($course->id), $category->id);
1824         $events = $sink->get_events();
1825         $sink->close();
1827         // Return the moved course information from the DB.
1828         $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1829         // Validate event.
1830         $event = array_shift($events);
1831         $this->assertInstanceOf('\core\event\course_updated', $event);
1832         $this->assertEquals('course', $event->objecttable);
1833         $this->assertEquals($movedcourse->id, $event->objectid);
1834         $this->assertEquals(context_course::instance($course->id), $event->get_context());
1835         $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
1836         $this->assertEquals('course_updated', $event->get_legacy_eventname());
1837         $this->assertEventLegacyData($movedcourse, $event);
1838         $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
1839         $this->assertEventLegacyLogData($expectedlog, $event);
1841         // Move course to hidden category and catch course_updated event.
1842         $sink = $this->redirectEvents();
1843         move_courses(array($course->id), $categoryhidden->id);
1844         $events = $sink->get_events();
1845         $sink->close();
1847         // Return the moved course information from the DB.
1848         $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1849         // Validate event.
1850         $event = array_shift($events);
1851         $this->assertInstanceOf('\core\event\course_updated', $event);
1852         $this->assertEquals('course', $event->objecttable);
1853         $this->assertEquals($movedcoursehidden->id, $event->objectid);
1854         $this->assertEquals(context_course::instance($course->id), $event->get_context());
1855         $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
1856         $this->assertEquals('course_updated', $event->get_legacy_eventname());
1857         $this->assertEventLegacyData($movedcoursehidden, $event);
1858         $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
1859         $this->assertEventLegacyLogData($expectedlog, $event);
1860         $this->assertEventContextNotUsed($event);
1861     }
1863     /**
1864      * Test that triggering a course_updated event logs changes.
1865      */
1866     public function test_course_updated_event_with_changes() {
1867         global $DB;
1869         $this->resetAfterTest();
1871         // Create a course.
1872         $course = $this->getDataGenerator()->create_course((object)['visible' => 1]);
1874         $editedcourse = $DB->get_record('course', ['id' => $course->id]);
1875         $editedcourse->visible = 0;
1877         // Update course and catch course_updated event.
1878         $sink = $this->redirectEvents();
1879         update_course($editedcourse);
1880         $events = $sink->get_events();
1881         $sink->close();
1883         $event = array_shift($events);
1884         $this->assertInstanceOf('\core\event\course_updated', $event);
1885         $otherdata = [
1886             'shortname' => $course->shortname,
1887             'fullname' => $course->fullname,
1888             'updatedfields' => [
1889                 'visible' => 0
1890             ]
1891         ];
1892         $this->assertEquals($otherdata, $event->other);
1894     }
1896     /**
1897      * Test that triggering a course_deleted event works as expected.
1898      */
1899     public function test_course_deleted_event() {
1900         $this->resetAfterTest();
1902         // Create the course.
1903         $course = $this->getDataGenerator()->create_course();
1905         // Save the course context before we delete the course.
1906         $coursecontext = context_course::instance($course->id);
1908         // Catch the update event.
1909         $sink = $this->redirectEvents();
1911         // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
1912         // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
1913         // so use ob_start and ob_end_clean to prevent this.
1914         ob_start();
1915         delete_course($course);
1916         ob_end_clean();
1918         // Capture the event.
1919         $events = $sink->get_events();
1920         $sink->close();
1922         // Validate the event.
1923         $event = array_pop($events);
1924         $this->assertInstanceOf('\core\event\course_deleted', $event);
1925         $this->assertEquals('course', $event->objecttable);
1926         $this->assertEquals($course->id, $event->objectid);
1927         $this->assertEquals($coursecontext->id, $event->contextid);
1928         $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1929         $this->assertEquals('course_deleted', $event->get_legacy_eventname());
1930         $eventdata = $event->get_data();
1931         $this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
1932         $this->assertSame($course->fullname, $eventdata['other']['fullname']);
1933         $this->assertSame($course->shortname, $eventdata['other']['shortname']);
1935         // The legacy data also passed the context in the course object and substitutes timemodified with the current date.
1936         $expectedlegacy = clone($course);
1937         $expectedlegacy->context = $coursecontext;
1938         $expectedlegacy->timemodified = $event->timecreated;
1939         $this->assertEventLegacyData($expectedlegacy, $event);
1941         // Validate legacy log data.
1942         $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
1943         $this->assertEventLegacyLogData($expectedlog, $event);
1944         $this->assertEventContextNotUsed($event);
1945     }
1947     /**
1948      * Test that triggering a course_content_deleted event works as expected.
1949      */
1950     public function test_course_content_deleted_event() {
1951         global $DB;
1953         $this->resetAfterTest();
1955         // Create the course.
1956         $course = $this->getDataGenerator()->create_course();
1958         // Get the course from the DB. The data generator adds some extra properties, such as
1959         // numsections, to the course object which will fail the assertions later on.
1960         $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
1962         // Save the course context before we delete the course.
1963         $coursecontext = context_course::instance($course->id);
1965         // Catch the update event.
1966         $sink = $this->redirectEvents();
1968         remove_course_contents($course->id, false);
1970         // Capture the event.
1971         $events = $sink->get_events();
1972         $sink->close();
1974         // Validate the event.
1975         $event = array_pop($events);
1976         $this->assertInstanceOf('\core\event\course_content_deleted', $event);
1977         $this->assertEquals('course', $event->objecttable);
1978         $this->assertEquals($course->id, $event->objectid);
1979         $this->assertEquals($coursecontext->id, $event->contextid);
1980         $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
1981         $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
1982         // The legacy data also passed the context and options in the course object.
1983         $course->context = $coursecontext;
1984         $course->options = array();
1985         $this->assertEventLegacyData($course, $event);
1986         $this->assertEventContextNotUsed($event);
1987     }
1989     /**
1990      * Test that triggering a course_category_deleted event works as expected.
1991      */
1992     public function test_course_category_deleted_event() {
1993         $this->resetAfterTest();
1995         // Create a category.
1996         $category = $this->getDataGenerator()->create_category();
1998         // Save the context before it is deleted.
1999         $categorycontext = context_coursecat::instance($category->id);
2001         // Catch the update event.
2002         $sink = $this->redirectEvents();
2004         // Delete the category.
2005         $category->delete_full();
2007         // Capture the event.
2008         $events = $sink->get_events();
2009         $sink->close();
2011         // Validate the event.
2012         $event = $events[0];
2013         $this->assertInstanceOf('\core\event\course_category_deleted', $event);
2014         $this->assertEquals('course_categories', $event->objecttable);
2015         $this->assertEquals($category->id, $event->objectid);
2016         $this->assertEquals($categorycontext->id, $event->contextid);
2017         $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
2018         $this->assertEquals(null, $event->get_url());
2019         $this->assertEventLegacyData($category, $event);
2020         $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
2021         $this->assertEventLegacyLogData($expectedlog, $event);
2023         // Create two categories.
2024         $category = $this->getDataGenerator()->create_category();
2025         $category2 = $this->getDataGenerator()->create_category();
2027         // Save the context before it is moved and then deleted.
2028         $category2context = context_coursecat::instance($category2->id);
2030         // Catch the update event.
2031         $sink = $this->redirectEvents();
2033         // Move the category.
2034         $category2->delete_move($category->id);
2036         // Capture the event.
2037         $events = $sink->get_events();
2038         $sink->close();
2040         // Validate the event.
2041         $event = $events[0];
2042         $this->assertInstanceOf('\core\event\course_category_deleted', $event);
2043         $this->assertEquals('course_categories', $event->objecttable);
2044         $this->assertEquals($category2->id, $event->objectid);
2045         $this->assertEquals($category2context->id, $event->contextid);
2046         $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
2047         $this->assertEventLegacyData($category2, $event);
2048         $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
2049         $this->assertEventLegacyLogData($expectedlog, $event);
2050         $this->assertEventContextNotUsed($event);
2051     }
2053     /**
2054      * Test that triggering a course_backup_created event works as expected.
2055      */
2056     public function test_course_backup_created_event() {
2057         global $CFG;
2059         // Get the necessary files to perform backup and restore.
2060         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
2061         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
2063         $this->resetAfterTest();
2065         // Set to admin user.
2066         $this->setAdminUser();
2068         // The user id is going to be 2 since we are the admin user.
2069         $userid = 2;
2071         // Create a course.
2072         $course = $this->getDataGenerator()->create_course();
2074         // Create backup file and save it to the backup location.
2075         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
2076             backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
2077         $sink = $this->redirectEvents();
2078         $bc->execute_plan();
2080         // Capture the event.
2081         $events = $sink->get_events();
2082         $sink->close();
2084         // Validate the event.
2085         $event = array_pop($events);
2086         $this->assertInstanceOf('\core\event\course_backup_created', $event);
2087         $this->assertEquals('course', $event->objecttable);
2088         $this->assertEquals($bc->get_courseid(), $event->objectid);
2089         $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid);
2091         $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
2092         $this->assertEquals($url, $event->get_url());
2093         $this->assertEventContextNotUsed($event);
2095         // Destroy the resource controller since we are done using it.
2096         $bc->destroy();
2097     }
2099     /**
2100      * Test that triggering a course_restored event works as expected.
2101      */
2102     public function test_course_restored_event() {
2103         global $CFG;
2105         // Get the necessary files to perform backup and restore.
2106         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
2107         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
2109         $this->resetAfterTest();
2111         // Set to admin user.
2112         $this->setAdminUser();
2114         // The user id is going to be 2 since we are the admin user.
2115         $userid = 2;
2117         // Create a course.
2118         $course = $this->getDataGenerator()->create_course();
2120         // Create backup file and save it to the backup location.
2121         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
2122             backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
2123         $bc->execute_plan();
2124         $results = $bc->get_results();
2125         $file = $results['backup_destination'];
2126         $fp = get_file_packer('application/vnd.moodle.backup');
2127         $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
2128         $file->extract_to_pathname($fp, $filepath);
2129         $bc->destroy();
2131         // Now we want to catch the restore course event.
2132         $sink = $this->redirectEvents();
2134         // Now restore the course to trigger the event.
2135         $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
2136             backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
2137         $rc->execute_precheck();
2138         $rc->execute_plan();
2140         // Capture the event.
2141         $events = $sink->get_events();
2142         $sink->close();
2144         // Validate the event.
2145         $event = array_pop($events);
2146         $this->assertInstanceOf('\core\event\course_restored', $event);
2147         $this->assertEquals('course', $event->objecttable);
2148         $this->assertEquals($rc->get_courseid(), $event->objectid);
2149         $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
2150         $this->assertEquals('course_restored', $event->get_legacy_eventname());
2151         $legacydata = (object) array(
2152             'courseid' => $rc->get_courseid(),
2153             'userid' => $rc->get_userid(),
2154             'type' => $rc->get_type(),
2155             'target' => $rc->get_target(),
2156             'mode' => $rc->get_mode(),
2157             'operation' => $rc->get_operation(),
2158             'samesite' => $rc->is_samesite()
2159         );
2160         $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
2161         $this->assertEquals($url, $event->get_url());
2162         $this->assertEventLegacyData($legacydata, $event);
2163         $this->assertEventContextNotUsed($event);
2165         // Destroy the resource controller since we are done using it.
2166         $rc->destroy();
2167     }
2169     /**
2170      * Test that triggering a course_section_updated event works as expected.
2171      */
2172     public function test_course_section_updated_event() {
2173         global $DB;
2175         $this->resetAfterTest();
2177         // Create the course with sections.
2178         $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
2179         $sections = $DB->get_records('course_sections', array('course' => $course->id));
2181         $coursecontext = context_course::instance($course->id);
2183         $section = array_pop($sections);
2184         $section->name = 'Test section';
2185         $section->summary = 'Test section summary';
2186         $DB->update_record('course_sections', $section);
2188         // Trigger an event for course section update.
2189         $event = \core\event\course_section_updated::create(
2190                 array(
2191                     'objectid' => $section->id,
2192                     'courseid' => $course->id,
2193                     'context' => context_course::instance($course->id),
2194                     'other' => array(
2195                         'sectionnum' => $section->section
2196                     )
2197                 )
2198             );
2199         $event->add_record_snapshot('course_sections', $section);
2200         // Trigger and catch event.
2201         $sink = $this->redirectEvents();
2202         $event->trigger();
2203         $events = $sink->get_events();
2204         $sink->close();
2206         // Validate the event.
2207         $event = $events[0];
2208         $this->assertInstanceOf('\core\event\course_section_updated', $event);
2209         $this->assertEquals('course_sections', $event->objecttable);
2210         $this->assertEquals($section->id, $event->objectid);
2211         $this->assertEquals($course->id, $event->courseid);
2212         $this->assertEquals($coursecontext->id, $event->contextid);
2213         $this->assertEquals($section->section, $event->other['sectionnum']);
2214         $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
2215         $this->assertEquals($expecteddesc, $event->get_description());
2216         $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
2217         $this->assertEquals($url, $event->get_url());
2218         $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
2219         $id = $section->id;
2220         $sectionnum = $section->section;
2221         $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
2222         $this->assertEventLegacyLogData($expectedlegacydata, $event);
2223         $this->assertEventContextNotUsed($event);
2224     }
2226     /**
2227      * Test that triggering a course_section_deleted event works as expected.
2228      */
2229     public function test_course_section_deleted_event() {
2230         global $USER, $DB;
2231         $this->resetAfterTest();
2232         $sink = $this->redirectEvents();
2234         // Create the course with sections.
2235         $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
2236         $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
2237         $coursecontext = context_course::instance($course->id);
2238         $section = array_pop($sections);
2239         course_delete_section($course, $section);
2240         $events = $sink->get_events();
2241         $event = array_pop($events); // Delete section event.
2242         $sink->close();
2244         // Validate event data.
2245         $this->assertInstanceOf('\core\event\course_section_deleted', $event);
2246         $this->assertEquals('course_sections', $event->objecttable);
2247         $this->assertEquals($section->id, $event->objectid);
2248         $this->assertEquals($course->id, $event->courseid);
2249         $this->assertEquals($coursecontext->id, $event->contextid);
2250         $this->assertEquals($section->section, $event->other['sectionnum']);
2251         $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " .
2252                 "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'";
2253         $this->assertEquals($expecteddesc, $event->get_description());
2254         $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
2255         $this->assertNull($event->get_url());
2257         // Test legacy data.
2258         $sectionnum = $section->section;
2259         $expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum);
2260         $this->assertEventLegacyLogData($expectedlegacydata, $event);
2261         $this->assertEventContextNotUsed($event);
2262     }
2264     public function test_course_integrity_check() {
2265         global $DB;
2267         $this->resetAfterTest(true);
2268         $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
2269            array('createsections'=>true));
2271         $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
2272                 array('section' => 0));
2273         $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
2274                 array('section' => 0));
2275         $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
2276                 array('section' => 0));
2277         $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
2279         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2280         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2281         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2282         $this->assertEquals($correctseq, $section0->sequence);
2283         $this->assertEmpty($section1->sequence);
2284         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2285         $this->assertEquals($section0->id, $cms[$page->cmid]->section);
2286         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2287         $this->assertEmpty(course_integrity_check($course->id));
2289         // Now let's make manual change in DB and let course_integrity_check() fix it:
2291         // 1. Module appears twice in one section.
2292         $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
2293         $this->assertEquals(
2294                 array('Failed integrity check for course ['. $course->id.
2295                 ']. Sequence for course section ['. $section0->id. '] is "'.
2296                 $section0->sequence. ','. $page->cmid. '", must be "'.
2297                 $section0->sequence. '"'),
2298                 course_integrity_check($course->id));
2299         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2300         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2301         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2302         $this->assertEquals($correctseq, $section0->sequence);
2303         $this->assertEmpty($section1->sequence);
2304         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2305         $this->assertEquals($section0->id, $cms[$page->cmid]->section);
2306         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2308         // 2. Module appears in two sections (last section wins).
2309         $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
2310         // First message about double mentioning in sequence, second message about wrong section field for $page.
2311         $this->assertEquals(array(
2312             'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2313             '] must be removed from sequence of section ['. $section0->id.
2314             '] because it is also present in sequence of section ['. $section1->id. ']',
2315             'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2316             '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
2317                 course_integrity_check($course->id));
2318         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2319         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2320         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2321         $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2322         $this->assertEquals(''. $page->cmid, $section1->sequence);
2323         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2324         $this->assertEquals($section1->id, $cms[$page->cmid]->section);
2325         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2327         // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
2328         $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
2329         $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
2330         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2331         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2332         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2333         $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2334         $this->assertEmpty($section1->sequence);
2335         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2336         $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
2337         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2339         // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
2340         $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
2341                 $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
2342                 course_integrity_check($course->id, null, null, true)); // Error!
2343         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2344         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2345         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2346         $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2347         $this->assertEquals(''. $page->cmid, $section1->sequence);  // Yay, module added to section.
2348         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2349         $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
2350         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2352         // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
2353         $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
2354         $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
2355         $this->assertEquals(array(
2356             'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2357             '] is missing from sequence of section ['. $section0->id. ']',
2358             'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2359             '] points to section [8765] instead of ['. $section0->id. ']'),
2360                 course_integrity_check($course->id, null, null, true));
2361         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2362         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2363         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2364         $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
2365         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2366         $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
2367         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2369         // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
2370         $DB->delete_records('course_modules', array('id' => $page->cmid));
2371         $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
2372                 $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
2373                 course_integrity_check($course->id, null, null, true));
2374         $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2375         $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2376         $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2377         $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2378         $this->assertEmpty($section1->sequence);
2379         $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2380         $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2381         $this->assertEquals(2, count($cms));
2382     }
2384     /**
2385      * Tests for event related to course module creation.
2386      */
2387     public function test_course_module_created_event() {
2388         global $USER;
2390         $this->resetAfterTest();
2391         $this->setAdminUser();
2393         // Create an assign module.
2394         $sink = $this->redirectEvents();
2395         $course = $this->getDataGenerator()->create_course();
2396         $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
2397         $events = $sink->get_events();
2398         $eventscount = 0;
2400         // Validate event data.
2401         foreach ($events as $event) {
2402             if ($event instanceof \core\event\course_module_created) {
2403                 $eventscount++;
2405                 $this->assertEquals($module->cmid, $event->objectid);
2406                 $this->assertEquals($USER->id, $event->userid);
2407                 $this->assertEquals('course_modules', $event->objecttable);
2408                 $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid));
2409                 $this->assertEquals($url, $event->get_url());
2411                 // Test legacy data.
2412                 $this->assertSame('mod_created', $event->get_legacy_eventname());
2413                 $eventdata = new stdClass();
2414                 $eventdata->modulename = 'assign';
2415                 $eventdata->name       = $module->name;
2416                 $eventdata->cmid       = $module->cmid;
2417                 $eventdata->courseid   = $module->course;
2418                 $eventdata->userid     = $USER->id;
2419                 $this->assertEventLegacyData($eventdata, $event);
2421                 $arr = array(
2422                     array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"),
2423                     array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid)
2424                 );
2425                 $this->assertEventLegacyLogData($arr, $event);
2426                 $this->assertEventContextNotUsed($event);
2427             }
2428         }
2429         // Only one \core\event\course_module_created event should be triggered.
2430         $this->assertEquals(1, $eventscount);
2432         // Let us see if duplicating an activity results in a nice course module created event.
2433         $sink->clear();
2434         $course = get_course($module->course);
2435         $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST);
2436         $newcm = duplicate_module($course, $cm);
2437         $events = $sink->get_events();
2438         $eventscount = 0;
2439         $sink->close();
2441         foreach ($events as $event) {
2442             if ($event instanceof \core\event\course_module_created) {
2443                 $eventscount++;
2444                 // Validate event data.
2445                 $this->assertInstanceOf('\core\event\course_module_created', $event);
2446                 $this->assertEquals($newcm->id, $event->objectid);
2447                 $this->assertEquals($USER->id, $event->userid);
2448                 $this->assertEquals($course->id, $event->courseid);
2449                 $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
2450                 $this->assertEquals($url, $event->get_url());
2451             }
2452         }
2454         // Only one \core\event\course_module_created event should be triggered.
2455         $this->assertEquals(1, $eventscount);
2456     }
2458     /**
2459      * Tests for event validations related to course module creation.
2460      */
2461     public function test_course_module_created_event_exceptions() {
2463         $this->resetAfterTest();
2465         // Generate data.
2466         $modinfo = $this->create_specific_module_test('assign');
2467         $context = context_module::instance($modinfo->coursemodule);
2469         // Test not setting instanceid.
2470         try {
2471             $event = \core\event\course_module_created::create(array(
2472                 'courseid' => $modinfo->course,
2473                 'context'  => $context,
2474                 'objectid' => $modinfo->coursemodule,
2475                 'other'    => array(
2476                     'modulename' => 'assign',
2477                     'name'       => 'My assignment',
2478                 )
2479             ));
2480             $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2481                     other['instanceid']");
2482         } catch (coding_exception $e) {
2483             $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2484         }
2486         // Test not setting modulename.
2487         try {
2488             $event = \core\event\course_module_created::create(array(
2489                 'courseid' => $modinfo->course,
2490                 'context'  => $context,
2491                 'objectid' => $modinfo->coursemodule,
2492                 'other'    => array(
2493                     'instanceid' => $modinfo->instance,
2494                     'name'       => 'My assignment',
2495                 )
2496             ));
2497             $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2498                     other['modulename']");
2499         } catch (coding_exception $e) {
2500             $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2501         }
2503         // Test not setting name.
2505         try {
2506             $event = \core\event\course_module_created::create(array(
2507                 'courseid' => $modinfo->course,
2508                 'context'  => $context,
2509                 'objectid' => $modinfo->coursemodule,
2510                 'other'    => array(
2511                     'modulename' => 'assign',
2512                     'instanceid' => $modinfo->instance,
2513                 )
2514             ));
2515             $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2516                     other['name']");
2517         } catch (coding_exception $e) {
2518             $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
2519         }
2521     }
2523     /**
2524      * Tests for event related to course module updates.
2525      */
2526     public function test_course_module_updated_event() {
2527         global $USER, $DB;
2528         $this->resetAfterTest();
2530         // Update a forum module.
2531         $sink = $this->redirectEvents();
2532         $modinfo = $this->update_specific_module_test('forum');
2533         $events = $sink->get_events();
2534         $eventscount = 0;
2535         $sink->close();
2537         $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2538         $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
2540         // Validate event data.
2541         foreach ($events as $event) {
2542             if ($event instanceof \core\event\course_module_updated) {
2543                 $eventscount++;
2545                 $this->assertEquals($cm->id, $event->objectid);
2546                 $this->assertEquals($USER->id, $event->userid);
2547                 $this->assertEquals('course_modules', $event->objecttable);
2548                 $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
2549                 $this->assertEquals($url, $event->get_url());
2551                 // Test legacy data.
2552                 $this->assertSame('mod_updated', $event->get_legacy_eventname());
2553                 $eventdata = new stdClass();
2554                 $eventdata->modulename = 'forum';
2555                 $eventdata->name       = $mod->name;
2556                 $eventdata->cmid       = $cm->id;
2557                 $eventdata->courseid   = $cm->course;
2558                 $eventdata->userid     = $USER->id;
2559                 $this->assertEventLegacyData($eventdata, $event);
2561                 $arr = array(
2562                     array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"),
2563                     array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
2564                 );
2565                 $this->assertEventLegacyLogData($arr, $event);
2566                 $this->assertEventContextNotUsed($event);
2567             }
2568         }
2570         // Only one \core\event\course_module_updated event should be triggered.
2571         $this->assertEquals(1, $eventscount);
2572     }
2574     /**
2575      * Tests for create_from_cm method.
2576      */
2577     public function test_course_module_create_from_cm() {
2578         $this->resetAfterTest();
2579         $this->setAdminUser();
2581         // Create course and modules.
2582         $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
2584         // Generate an assignment.
2585         $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
2587         // Get the module context.
2588         $modcontext = context_module::instance($assign->cmid);
2590         // Get course module.
2591         $cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST);
2593         // Create an event from course module.
2594         $event = \core\event\course_module_updated::create_from_cm($cm, $modcontext);
2596         // Trigger the events.
2597         $sink = $this->redirectEvents();
2598         $event->trigger();
2599         $events = $sink->get_events();
2600         $event2 = array_pop($events);
2602         // Test event data.
2603         $this->assertInstanceOf('\core\event\course_module_updated', $event);
2604         $this->assertEquals($cm->id, $event2->objectid);
2605         $this->assertEquals($modcontext, $event2->get_context());
2606         $this->assertEquals($cm->modname, $event2->other['modulename']);
2607         $this->assertEquals($cm->instance, $event2->other['instanceid']);
2608         $this->assertEquals($cm->name, $event2->other['name']);
2609         $this->assertEventContextNotUsed($event2);
2610         $this->assertSame('mod_updated', $event2->get_legacy_eventname());
2611         $arr = array(
2612             array($cm->course, "course", "update mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"),
2613             array($cm->course, "assign", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
2614         );
2615         $this->assertEventLegacyLogData($arr, $event);
2616     }
2618     /**
2619      * Tests for event validations related to course module update.
2620      */
2621     public function test_course_module_updated_event_exceptions() {
2623         $this->resetAfterTest();
2625         // Generate data.
2626         $modinfo = $this->create_specific_module_test('assign');
2627         $context = context_module::instance($modinfo->coursemodule);
2629         // Test not setting instanceid.
2630         try {
2631             $event = \core\event\course_module_updated::create(array(
2632                 'courseid' => $modinfo->course,
2633                 'context'  => $context,
2634                 'objectid' => $modinfo->coursemodule,
2635                 'other'    => array(
2636                     'modulename' => 'assign',
2637                     'name'       => 'My assignment',
2638                 )
2639             ));
2640             $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2641                     other['instanceid']");
2642         } catch (coding_exception $e) {
2643             $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2644         }
2646         // Test not setting modulename.
2647         try {
2648             $event = \core\event\course_module_updated::create(array(
2649                 'courseid' => $modinfo->course,
2650                 'context'  => $context,
2651                 'objectid' => $modinfo->coursemodule,
2652                 'other'    => array(
2653                     'instanceid' => $modinfo->instance,
2654                     'name'       => 'My assignment',
2655                 )
2656             ));
2657             $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2658                     other['modulename']");
2659         } catch (coding_exception $e) {
2660             $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2661         }
2663         // Test not setting name.
2665         try {
2666             $event = \core\event\course_module_updated::create(array(
2667                 'courseid' => $modinfo->course,
2668                 'context'  => $context,
2669                 'objectid' => $modinfo->coursemodule,
2670                 'other'    => array(
2671                     'modulename' => 'assign',
2672                     'instanceid' => $modinfo->instance,
2673                 )
2674             ));
2675             $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2676                     other['name']");
2677         } catch (coding_exception $e) {
2678             $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
2679         }
2681     }
2683     /**
2684      * Tests for event related to course module delete.
2685      */
2686     public function test_course_module_deleted_event() {
2687         global $USER, $DB;
2688         $this->resetAfterTest();
2690         // Create and delete a module.
2691         $sink = $this->redirectEvents();
2692         $modinfo = $this->create_specific_module_test('forum');
2693         $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2694         course_delete_module($modinfo->coursemodule);
2695         $events = $sink->get_events();
2696         $event = array_pop($events); // delete module event.;
2697         $sink->close();
2699         // Validate event data.
2700         $this->assertInstanceOf('\core\event\course_module_deleted', $event);
2701         $this->assertEquals($cm->id, $event->objectid);
2702         $this->assertEquals($USER->id, $event->userid);
2703         $this->assertEquals('course_modules', $event->objecttable);
2704         $this->assertEquals(null, $event->get_url());
2705         $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
2707         // Test legacy data.
2708         $this->assertSame('mod_deleted', $event->get_legacy_eventname());
2709         $eventdata = new stdClass();
2710         $eventdata->modulename = 'forum';
2711         $eventdata->cmid       = $cm->id;
2712         $eventdata->courseid   = $cm->course;
2713         $eventdata->userid     = $USER->id;
2714         $this->assertEventLegacyData($eventdata, $event);
2716         $arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id);
2717         $this->assertEventLegacyLogData($arr, $event);
2719     }
2721     /**
2722      * Tests for event validations related to course module deletion.
2723      */
2724     public function test_course_module_deleted_event_exceptions() {
2726         $this->resetAfterTest();
2728         // Generate data.
2729         $modinfo = $this->create_specific_module_test('assign');
2730         $context = context_module::instance($modinfo->coursemodule);
2732         // Test not setting instanceid.
2733         try {
2734             $event = \core\event\course_module_deleted::create(array(
2735                 'courseid' => $modinfo->course,
2736                 'context'  => $context,
2737                 'objectid' => $modinfo->coursemodule,
2738                 'other'    => array(
2739                     'modulename' => 'assign',
2740                     'name'       => 'My assignment',
2741                 )
2742             ));
2743             $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2744                     other['instanceid']");
2745         } catch (coding_exception $e) {
2746             $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2747         }
2749         // Test not setting modulename.
2750         try {
2751             $event = \core\event\course_module_deleted::create(array(
2752                 'courseid' => $modinfo->course,
2753                 'context'  => $context,
2754                 'objectid' => $modinfo->coursemodule,
2755                 'other'    => array(
2756                     'instanceid' => $modinfo->instance,
2757                     'name'       => 'My assignment',
2758                 )
2759             ));
2760             $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2761                     other['modulename']");
2762         } catch (coding_exception $e) {
2763             $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2764         }
2765     }
2767     /**
2768      * Returns a user object and its assigned new role.
2769      *
2770      * @param testing_data_generator $generator
2771      * @param $contextid
2772      * @return array The user object and the role ID
2773      */
2774     protected function get_user_objects(testing_data_generator $generator, $contextid) {
2775         global $USER;
2777         if (empty($USER->id)) {
2778             $user  = $generator->create_user();
2779             $this->setUser($user);
2780         }
2781         $roleid = create_role('Test role', 'testrole', 'Test role description');
2782         if (!is_array($contextid)) {
2783             $contextid = array($contextid);
2784         }
2785         foreach ($contextid as $cid) {
2786             $assignid = role_assign($roleid, $user->id, $cid);
2787         }
2788         return array($user, $roleid);
2789     }
2791     /**
2792      * Test course move after course.
2793      */
2794     public function test_course_change_sortorder_after_course() {
2795         global $DB;
2797         $this->resetAfterTest(true);
2799         $generator = $this->getDataGenerator();
2800         $category = $generator->create_category();
2801         $course3 = $generator->create_course(array('category' => $category->id));
2802         $course2 = $generator->create_course(array('category' => $category->id));
2803         $course1 = $generator->create_course(array('category' => $category->id));
2804         $context = $category->get_context();
2806         list($user, $roleid) = $this->get_user_objects($generator, $context->id);
2807         $caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id);
2809         $courses = $category->get_courses();
2810         $this->assertIsArray($courses);
2811         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2812         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2813         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2815         // Test moving down.
2816         $this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id));
2817         $courses = $category->get_courses();
2818         $this->assertIsArray($courses);
2819         $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
2820         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2821         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2823         // Test moving up.
2824         $this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id));
2825         $courses = $category->get_courses();
2826         $this->assertIsArray($courses);
2827         $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
2828         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2829         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2831         // Test moving to the top.
2832         $this->assertTrue(course_change_sortorder_after_course($course1->id, 0));
2833         $courses = $category->get_courses();
2834         $this->assertIsArray($courses);
2835         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2836         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2837         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2838     }
2840     /**
2841      * Tests changing the visibility of a course.
2842      */
2843     public function test_course_change_visibility() {
2844         global $DB;
2846         $this->resetAfterTest(true);
2848         $generator = $this->getDataGenerator();
2849         $category = $generator->create_category();
2850         $course = $generator->create_course(array('category' => $category->id));
2852         $this->assertEquals('1', $course->visible);
2853         $this->assertEquals('1', $course->visibleold);
2855         $this->assertTrue(course_change_visibility($course->id, false));
2856         $course = $DB->get_record('course', array('id' => $course->id));
2857         $this->assertEquals('0', $course->visible);
2858         $this->assertEquals('0', $course->visibleold);
2860         $this->assertTrue(course_change_visibility($course->id, true));
2861         $course = $DB->get_record('course', array('id' => $course->id));
2862         $this->assertEquals('1', $course->visible);
2863         $this->assertEquals('1', $course->visibleold);
2864     }
2866     /**
2867      * Tests moving the course up and down by one.
2868      */
2869     public function test_course_change_sortorder_by_one() {
2870         global $DB;
2872         $this->resetAfterTest(true);
2874         $generator = $this->getDataGenerator();
2875         $category = $generator->create_category();
2876         $course3 = $generator->create_course(array('category' => $category->id));
2877         $course2 = $generator->create_course(array('category' => $category->id));
2878         $course1 = $generator->create_course(array('category' => $category->id));
2880         $courses = $category->get_courses();
2881         $this->assertIsArray($courses);
2882         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2883         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2884         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2886         // Test moving down.
2887         $course1 = get_course($course1->id);
2888         $this->assertTrue(course_change_sortorder_by_one($course1, false));
2889         $courses = $category->get_courses();
2890         $this->assertIsArray($courses);
2891         $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
2892         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2893         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2895         // Test moving up.
2896         $course1 = get_course($course1->id);
2897         $this->assertTrue(course_change_sortorder_by_one($course1, true));
2898         $courses = $category->get_courses();
2899         $this->assertIsArray($courses);
2900         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2901         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2902         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2904         // Test moving the top course up one.
2905         $course1 = get_course($course1->id);
2906         $this->assertFalse(course_change_sortorder_by_one($course1, true));
2907         // Check nothing changed.
2908         $courses = $category->get_courses();
2909         $this->assertIsArray($courses);
2910         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2911         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2912         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2914         // Test moving the bottom course up down.
2915         $course3 = get_course($course3->id);
2916         $this->assertFalse(course_change_sortorder_by_one($course3, false));
2917         // Check nothing changed.
2918         $courses = $category->get_courses();
2919         $this->assertIsArray($courses);
2920         $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2921         $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2922         $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2923     }
2925     public function test_view_resources_list() {
2926         $this->resetAfterTest();
2928         $course = self::getDataGenerator()->create_course();
2929         $coursecontext = context_course::instance($course->id);
2931         $event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id)));
2932         $event->set_legacy_logdata(array('book', 'page', 'resource'));
2933         $sink = $this->redirectEvents();
2934         $event->trigger();
2935         $events = $sink->get_events();
2936         $sink->close();
2938         // Validate the event.
2939         $event = $events[0];
2940         $this->assertInstanceOf('\core\event\course_resources_list_viewed', $event);
2941         $this->assertEquals(null, $event->objecttable);
2942         $this->assertEquals(null, $event->objectid);
2943         $this->assertEquals($course->id, $event->courseid);
2944         $this->assertEquals($coursecontext->id, $event->contextid);
2945         $expectedlegacydata = array(
2946             array($course->id, "book", "view all", 'index.php?id=' . $course->id, ''),
2947             array($course->id, "page", "view all", 'index.php?id=' . $course->id, ''),
2948             array($course->id, "resource", "view all", 'index.php?id=' . $course->id, ''),
2949         );
2950         $this->assertEventLegacyLogData($expectedlegacydata, $event);
2951         $this->assertEventContextNotUsed($event);
2952     }
2954     /**
2955      * Test duplicate_module()
2956      */
2957     public function test_duplicate_module() {
2958         $this->setAdminUser();
2959         $this->resetAfterTest();
2960         $course = self::getDataGenerator()->create_course();
2961         $res = self::getDataGenerator()->create_module('resource', array('course' => $course));
2962         $cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST);
2964         $newcm = duplicate_module($course, $cm);
2966         // Make sure they are the same, except obvious id changes.
2967         foreach ($cm as $prop => $value) {
2968             if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') {
2969                 // Ignore obviously different properties.
2970                 continue;
2971             }
2972             if ($prop == 'name') {
2973                 // We expect ' (copy)' to be added to the original name since MDL-59227.
2974                 $value = get_string('duplicatedmodule', 'moodle', $value);
2975             }
2976             $this->assertEquals($value, $newcm->$prop);
2977         }
2978     }
2980     /**
2981      * Tests that when creating or updating a module, if the availability settings
2982      * are present but set to an empty tree, availability is set to null in
2983      * database.
2984      */
2985     public function test_empty_availability_settings() {
2986         global $DB;
2987         $this->setAdminUser();
2988         $this->resetAfterTest();
2990         // Enable availability.
2991         set_config('enableavailability', 1);
2993         // Test add.
2994         $emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
2995         $course = self::getDataGenerator()->create_course();
2996         $label = self::getDataGenerator()->create_module('label', array(
2997                 'course' => $course, 'availability' => $emptyavailability));
2998         $this->assertNull($DB->get_field('course_modules', 'availability',
2999                 array('id' => $label->cmid)));
3001         // Test update.
3002         $formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
3003         unset($formdata->availability);
3004         $formdata->availabilityconditionsjson = $emptyavailability;
3005         $formdata->modulename = 'label';
3006         $formdata->coursemodule = $label->cmid;
3007         $draftid = 0;
3008         file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
3009                 'mod_label', 'intro', 0);
3010         $formdata->introeditor = array(
3011             'itemid' => $draftid,
3012             'text' => '<p>Yo</p>',
3013             'format' => FORMAT_HTML);
3014         update_module($formdata);
3015         $this->assertNull($DB->get_field('course_modules', 'availability',
3016                 array('id' => $label->cmid)));
3017     }
3019     /**
3020      * Test update_inplace_editable()
3021      */
3022     public function test_update_module_name_inplace() {
3023         global $CFG, $DB, $PAGE;
3024         require_once($CFG->dirroot . '/lib/external/externallib.php');
3026         $this->setUser($this->getDataGenerator()->create_user());
3028         $this->resetAfterTest(true);
3029         $course = $this->getDataGenerator()->create_course();
3030         $forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
3032         // Call service for core_course component without necessary permissions.
3033         try {
3034             core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
3035             $this->fail('Exception expected');
3036         } catch (moodle_exception $e) {
3037             $this->assertEquals('Course or activity not accessible. (Not enrolled)',
3038                 $e->getMessage());
3039         }
3041         // Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
3042         $this->setAdminUser();
3043         $res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
3044         $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
3045         $this->assertEquals('New forum name', $res['value']);
3046         $this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
3047     }
3049     /**
3050      * Testing function course_get_tagged_course_modules - search tagged course modules
3051      */
3052     public function test_course_get_tagged_course_modules() {
3053         global $DB;
3054         $this->resetAfterTest();
3055         $course3 = $this->getDataGenerator()->create_course();
3056         $course2 = $this->getDataGenerator()->create_course();
3057         $course1 = $this->getDataGenerator()->create_course();
3058         $cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
3059             'tags' => 'Cat, Dog'));
3060         $cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3061             'tags' => 'Cat, Mouse', 'visible' => 0));
3062         $cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3063             'tags' => 'Cat, Mouse, Dog'));
3064         $cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id,
3065             'tags' => 'Cat, Mouse'));
3066         $cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id,
3067             'tags' => 'Cat, Mouse'));
3069         // Admin is able to view everything.
3070         $this->setAdminUser();
3071         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3072                 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3073         $this->assertRegExp('/'.$cm11->name.'/', $res->content);
3074         $this->assertRegExp('/'.$cm12->name.'/', $res->content);
3075         $this->assertRegExp('/'.$cm13->name.'/', $res->content);
3076         $this->assertRegExp('/'.$cm21->name.'/', $res->content);
3077         $this->assertRegExp('/'.$cm31->name.'/', $res->content);
3078         // Results from course1 are returned before results from course2.
3079         $this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name));
3081         // Ordinary user is not able to see anything.
3082         $user = $this->getDataGenerator()->create_user();
3083         $this->setUser($user);
3085         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3086                 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3087         $this->assertNull($res);
3089         // Enrol user as student in course1 and course2.
3090         $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
3091         $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']);
3092         $this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']);
3093         core_tag_index_builder::reset_caches();
3095         // Searching in the course context returns visible modules in this course.
3096         $context = context_course::instance($course1->id);
3097         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3098                 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
3099         $this->assertRegExp('/'.$cm11->name.'/', $res->content);
3100         $this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
3101         $this->assertRegExp('/'.$cm13->name.'/', $res->content);
3102         $this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
3103         $this->assertNotRegExp('/'.$cm31->name.'/', $res->content);
3105         // Searching FROM the course context returns visible modules in all courses.
3106         $context = context_course::instance($course2->id);
3107         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3108                 /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3109         $this->assertRegExp('/'.$cm11->name.'/', $res->content);
3110         $this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
3111         $this->assertRegExp('/'.$cm13->name.'/', $res->content);
3112         $this->assertRegExp('/'.$cm21->name.'/', $res->content);
3113         $this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
3114         // Results from course2 are returned before results from course1.
3115         $this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name));
3117         // Enrol user in course1 as a teacher - now he should be able to see hidden module.
3118         $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']);
3119         get_fast_modinfo(0,0,true);
3121         $context = context_course::instance($course1->id);
3122         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3123                 /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3124         $this->assertRegExp('/'.$cm12->name.'/', $res->content);
3126         // Create more modules and try pagination.
3127         $cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
3128             'tags' => 'Cat, Dog'));
3129         $cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3130             'tags' => 'Cat, Mouse', 'visible' => 0));
3131         $cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3132             'tags' => 'Cat, Mouse, Dog'));
3134         $context = context_course::instance($course1->id);
3135         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3136                 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
3137         $this->assertRegExp('/'.$cm11->name.'/', $res->content);
3138         $this->assertRegExp('/'.$cm12->name.'/', $res->content);
3139         $this->assertRegExp('/'.$cm13->name.'/', $res->content);
3140         $this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
3141         $this->assertRegExp('/'.$cm14->name.'/', $res->content);
3142         $this->assertRegExp('/'.$cm15->name.'/', $res->content);
3143         $this->assertNotRegExp('/'.$cm16->name.'/', $res->content);
3144         $this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
3145         $this->assertEmpty($res->prevpageurl);
3146         $this->assertNotEmpty($res->nextpageurl);
3148         $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3149                 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1);
3150         $this->assertNotRegExp('/'.$cm11->name.'/', $res->content);
3151         $this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
3152         $this->assertNotRegExp('/'.$cm13->name.'/', $res->content);
3153         $this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
3154         $this->assertNotRegExp('/'.$cm14->name.'/', $res->content);
3155         $this->assertNotRegExp('/'.$cm15->name.'/', $res->content);
3156         $this->assertRegExp('/'.$cm16->name.'/', $res->content);
3157         $this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
3158         $this->assertNotEmpty($res->prevpageurl);
3159         $this->assertEmpty($res->nextpageurl);
3160     }
3162     /**
3163      * Test course_get_user_navigation_options for frontpage.
3164      */
3165     public function test_course_get_user_navigation_options_for_frontpage() {
3166         global $CFG, $SITE, $DB;
3167         $this->resetAfterTest();
3168         $context = context_system::instance();
3169         $course = clone $SITE;
3170         $this->setAdminUser();
3172         $navoptions = course_get_user_navigation_options($context, $course);
3173         $this->assertTrue($navoptions->blogs);
3174         $this->assertTrue($navoptions->notes);
3175         $this->assertTrue($navoptions->participants);
3176         $this->assertTrue($navoptions->badges);
3177         $this->assertTrue($navoptions->tags);
3178         $this->assertFalse($navoptions->search);
3179         $this->assertTrue($navoptions->calendar);
3180         $this->assertTrue($navoptions->competencies);
3182         // Enable global search now.
3183         $CFG->enableglobalsearch = 1;
3184         $navoptions = course_get_user_navigation_options($context, $course);
3185         $this->assertTrue($navoptions->search);
3187         // Disable competencies.
3188         $oldcompetencies = get_config('core_competency', 'enabled');
3189         set_config('enabled', false, 'core_competency');
3190         $navoptions = course_get_user_navigation_options($context, $course);
3191         $this->assertFalse($navoptions->competencies);
3192         set_config('enabled', $oldcompetencies, 'core_competency');
3194         // Now try with a standard user.
3195         $user = $this->getDataGenerator()->create_user();
3196         $this->setUser($user);
3197         $navoptions = course_get_user_navigation_options($context, $course);
3198         $this->assertTrue($navoptions->blogs);
3199         $this->assertFalse($navoptions->notes);
3200         $this->assertFalse($navoptions->participants);
3201         $this->assertTrue($navoptions->badges);
3202         $this->assertTrue($navoptions->tags);
3203         $this->assertTrue($navoptions->search);
3204         $this->assertTrue($navoptions->calendar);
3205     }
3207     /**
3208      * Test course_get_user_navigation_options for managers in a normal course.
3209      */
3210     public function test_course_get_user_navigation_options_for_managers() {
3211         global $CFG;
3212         $this->resetAfterTest();
3213         $course = $this->getDataGenerator()->create_course();
3214         $context = context_course::instance($course->id);
3215         $this->setAdminUser();
3217         $navoptions = course_get_user_navigation_options($context);
3218         $this->assertTrue($navoptions->blogs);
3219         $this->assertTrue($navoptions->notes);
3220         $this->assertTrue($navoptions->participants);
3221         $this->assertTrue($navoptions->badges);
3222     }
3224     /**
3225      * Test course_get_user_navigation_options for students in a normal course.
3226      */
3227     public function test_course_get_user_navigation_options_for_students() {
3228         global $DB, $CFG;
3229         $this->resetAfterTest();
3230         $course = $this->getDataGenerator()->create_course();
3231         $context = context_course::instance($course->id);
3233         $user = $this->getDataGenerator()->create_user();
3234         $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
3235         $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
3237         $this->setUser($user);
3239         $navoptions = course_get_user_navigation_options($context);
3240         $this->assertTrue($navoptions->blogs);
3241         $this->assertFalse($navoptions->notes);
3242         $this->assertTrue($navoptions->participants);
3243         $this->assertTrue($navoptions->badges);
3245         // Disable some options.
3246         $CFG->badges_allowcoursebadges = 0;
3247         $CFG->enableblogs = 0;
3248         // Disable view participants capability.
3249         assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $context);
3251         $navoptions = course_get_user_navigation_options($context);
3252         $this->assertFalse($navoptions->blogs);
3253         $this->assertFalse($navoptions->notes);
3254         $this->assertFalse($navoptions->participants);
3255         $this->assertFalse($navoptions->badges);
3256     }
3258     /**
3259      * Test course_get_user_administration_options for frontpage.
3260      */
3261     public function test_course_get_user_administration_options_for_frontpage() {
3262         global $CFG, $SITE;
3263         $this->resetAfterTest();
3264         $course = clone $SITE;
3265         $context = context_course::instance($course->id);
3266         $this->setAdminUser();
3268         $adminoptions = course_get_user_administration_options($course, $context);
3269         $this->assertTrue($adminoptions->update);
3270         $this->assertTrue($adminoptions->filters);
3271         $th