1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Unit tests for lib/modinfolib.php.
19 *
20 * @package    core
21 * @category   phpunit
22 * @copyright  2012 Andrew Davis
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27global $CFG;
28require_once($CFG->libdir . '/modinfolib.php');
29
30/**
31 * Unit tests for modinfolib.php
32 *
33 * @copyright 2012 Andrew Davis
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
36class core_modinfolib_testcase extends advanced_testcase {
37    public function test_section_info_properties() {
38        global $DB, $CFG;
39
40        $this->resetAfterTest();
41        $oldcfgenableavailability = $CFG->enableavailability;
42        $oldcfgenablecompletion = $CFG->enablecompletion;
43        set_config('enableavailability', 1);
44        set_config('enablecompletion', 1);
45        $this->setAdminUser();
46
47        // Generate the course and pre-requisite module.
48        $course = $this->getDataGenerator()->create_course(
49                array('format' => 'topics',
50                    'numsections' => 3,
51                    'enablecompletion' => 1,
52                    'groupmode' => SEPARATEGROUPS,
53                    'forcegroupmode' => 0),
54                array('createsections' => true));
55        $coursecontext = context_course::instance($course->id);
56        $prereqforum = $this->getDataGenerator()->create_module('forum',
57                array('course' => $course->id),
58                array('completion' => 1));
59
60        // Add availability conditions.
61        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
62                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
63                    COMPLETION_COMPLETE . '"},' .
64                '{"type":"grade","id":666,"min":0.4},' .
65                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
66                ']}';
67        $DB->set_field('course_sections', 'availability', $availability,
68                array('course' => $course->id, 'section' => 2));
69        rebuild_course_cache($course->id, true);
70        $sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
71
72        // Create and enrol a student.
73        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
74        $student = $this->getDataGenerator()->create_user();
75        role_assign($studentrole->id, $student->id, $coursecontext);
76        $enrolplugin = enrol_get_plugin('manual');
77        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
78        $enrolplugin->enrol_user($enrolinstance, $student->id);
79        $this->setUser($student);
80
81        // Get modinfo.
82        $modinfo = get_fast_modinfo($course->id);
83        $si = $modinfo->get_section_info(2);
84
85        $this->assertEquals($sectiondb->id, $si->id);
86        $this->assertEquals($sectiondb->course, $si->course);
87        $this->assertEquals($sectiondb->section, $si->section);
88        $this->assertEquals($sectiondb->name, $si->name);
89        $this->assertEquals($sectiondb->visible, $si->visible);
90        $this->assertEquals($sectiondb->summary, $si->summary);
91        $this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
92        $this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
93        $this->assertEquals($availability, $si->availability);
94
95        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
96        $this->assertEquals(0, $si->available);
97        $this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
98        $this->assertEquals(0, $si->uservisible);
99
100        // Restore settings.
101        set_config('enableavailability', $oldcfgenableavailability);
102        set_config('enablecompletion', $oldcfgenablecompletion);
103    }
104
105    public function test_cm_info_properties() {
106        global $DB, $CFG;
107
108        $this->resetAfterTest();
109        $oldcfgenableavailability = $CFG->enableavailability;
110        $oldcfgenablecompletion = $CFG->enablecompletion;
111        set_config('enableavailability', 1);
112        set_config('enablecompletion', 1);
113        $this->setAdminUser();
114
115        // Generate the course and pre-requisite module.
116        $course = $this->getDataGenerator()->create_course(
117                array('format' => 'topics',
118                    'numsections' => 3,
119                    'enablecompletion' => 1,
120                    'groupmode' => SEPARATEGROUPS,
121                    'forcegroupmode' => 0),
122                array('createsections' => true));
123        $coursecontext = context_course::instance($course->id);
124        $prereqforum = $this->getDataGenerator()->create_module('forum',
125                array('course' => $course->id),
126                array('completion' => 1));
127
128        // Generate module and add availability conditions.
129        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
130                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
131                    COMPLETION_COMPLETE . '"},' .
132                '{"type":"grade","id":666,"min":0.4},' .
133                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
134                ']}';
135        $assign = $this->getDataGenerator()->create_module('assign',
136                array('course' => $course->id),
137                array('idnumber' => 123,
138                    'groupmode' => VISIBLEGROUPS,
139                    'availability' => $availability));
140        rebuild_course_cache($course->id, true);
141
142        // Retrieve all related records from DB.
143        $assigndb = $DB->get_record('assign', array('id' => $assign->id));
144        $moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
145        $moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
146        $sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
147        $modnamessingular = get_module_types_names(false);
148        $modnamesplural = get_module_types_names(true);
149
150        // Create and enrol a student.
151        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
152        $student = $this->getDataGenerator()->create_user();
153        role_assign($studentrole->id, $student->id, $coursecontext);
154        $enrolplugin = enrol_get_plugin('manual');
155        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
156        $enrolplugin->enrol_user($enrolinstance, $student->id);
157        $this->setUser($student);
158
159        // Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
160        $rawmods = get_course_mods($course->id);
161        $cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
162
163        // Get modinfo.
164        $modinfo = get_fast_modinfo($course->id);
165        $cm = $modinfo->instances['assign'][$assign->id];
166
167        $this->assertEquals($moduledb->id, $cm->id);
168        $this->assertEquals($assigndb->id, $cm->instance);
169        $this->assertEquals($moduledb->course, $cm->course);
170        $this->assertEquals($moduledb->idnumber, $cm->idnumber);
171        $this->assertEquals($moduledb->added, $cm->added);
172        $this->assertEquals($moduledb->visible, $cm->visible);
173        $this->assertEquals($moduledb->visibleold, $cm->visibleold);
174        $this->assertEquals($moduledb->groupmode, $cm->groupmode);
175        $this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
176        $this->assertEquals($moduledb->groupingid, $cm->groupingid);
177        $this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
178        $this->assertEquals($course->groupmode, $cm->coursegroupmode);
179        $this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
180        $this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
181                $cm->effectivegroupmode); // (since mod_assign supports groups).
182        $this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
183        $this->assertEquals($moduledb->indent, $cm->indent);
184        $this->assertEquals($moduledb->completion, $cm->completion);
185        $this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
186        $this->assertEquals($moduledb->completionview, $cm->completionview);
187        $this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
188        $this->assertEquals($moduledb->showdescription, $cm->showdescription);
189        $this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
190        $this->assertEquals($cachedcminfo->icon, $cm->icon);
191        $this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
192        $this->assertEquals('assign', $cm->modname);
193        $this->assertEquals($moduledb->module, $cm->module);
194        $this->assertEquals($cachedcminfo->name, $cm->name);
195        $this->assertEquals($sectiondb->section, $cm->sectionnum);
196        $this->assertEquals($moduledb->section, $cm->section);
197        $this->assertEquals($availability, $cm->availability);
198        $this->assertEquals(context_module::instance($moduledb->id), $cm->context);
199        $this->assertEquals($modnamessingular['assign'], $cm->modfullname);
200        $this->assertEquals($modnamesplural['assign'], $cm->modplural);
201        $this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
202        $this->assertEquals($cachedcminfo->customdata, $cm->customdata);
203
204        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
205        $this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
206        $this->assertEquals(0, $cm->uservisible);
207        $this->assertEquals('', $cm->extraclasses);
208        $this->assertEquals('', $cm->onclick);
209        $this->assertEquals(null, $cm->afterlink);
210        $this->assertEquals(null, $cm->afterediticons);
211        $this->assertEquals('', $cm->content);
212
213        // Attempt to access and set non-existing field.
214        $this->assertTrue(empty($modinfo->somefield));
215        $this->assertFalse(isset($modinfo->somefield));
216        $cm->somefield;
217        $this->assertDebuggingCalled();
218        $cm->somefield = 'Some value';
219        $this->assertDebuggingCalled();
220        $this->assertEmpty($cm->somefield);
221        $this->assertDebuggingCalled();
222
223        // Attempt to overwrite an existing field.
224        $prevvalue = $cm->name;
225        $this->assertNotEmpty($cm->name);
226        $this->assertFalse(empty($cm->name));
227        $this->assertTrue(isset($cm->name));
228        $cm->name = 'Illegal overwriting';
229        $this->assertDebuggingCalled();
230        $this->assertEquals($prevvalue, $cm->name);
231        $this->assertDebuggingNotCalled();
232
233        // Restore settings.
234        set_config('enableavailability', $oldcfgenableavailability);
235        set_config('enablecompletion', $oldcfgenablecompletion);
236    }
237
238    public function test_matching_cacherev() {
239        global $DB, $CFG;
240
241        $this->resetAfterTest();
242        $this->setAdminUser();
243        $cache = cache::make('core', 'coursemodinfo');
244
245        // Generate the course and pre-requisite module.
246        $course = $this->getDataGenerator()->create_course(
247                array('format' => 'topics',
248                    'numsections' => 3),
249                array('createsections' => true));
250
251        // Make sure the cacherev is set.
252        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
253        $this->assertGreaterThan(0, $cacherev);
254        $prevcacherev = $cacherev;
255
256        // Reset course cache and make sure cacherev is bumped up but cache is empty.
257        rebuild_course_cache($course->id, true);
258        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
259        $this->assertGreaterThan($prevcacherev, $cacherev);
260        $this->assertEmpty($cache->get($course->id));
261        $prevcacherev = $cacherev;
262
263        // Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.
264        $modinfo = get_fast_modinfo($course->id);
265        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
266        $this->assertEquals($prevcacherev, $cacherev);
267        $cachedvalue = $cache->get($course->id);
268        $this->assertNotEmpty($cachedvalue);
269        $this->assertEquals($cacherev, $cachedvalue->cacherev);
270        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
271        $prevcacherev = $cacherev;
272
273        // Little trick to check that cache is not rebuilt druing the next step - substitute the value in MUC and later check that it is still there.
274        $cache->set($course->id, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));
275
276        // Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.
277        course_modinfo::clear_instance_cache();
278        $modinfo = get_fast_modinfo($course->id);
279        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
280        $this->assertEquals($prevcacherev, $cacherev);
281        $cachedvalue = $cache->get($course->id);
282        $this->assertNotEmpty($cachedvalue);
283        $this->assertEquals($cacherev, $cachedvalue->cacherev);
284        $this->assertNotEmpty($cachedvalue->secretfield);
285        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
286        $prevcacherev = $cacherev;
287
288        // Rebuild course cache. Cacherev must be incremented everywhere.
289        rebuild_course_cache($course->id);
290        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
291        $this->assertGreaterThan($prevcacherev, $cacherev);
292        $cachedvalue = $cache->get($course->id);
293        $this->assertNotEmpty($cachedvalue);
294        $this->assertEquals($cacherev, $cachedvalue->cacherev);
295        $modinfo = get_fast_modinfo($course->id);
296        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
297        $prevcacherev = $cacherev;
298
299        // Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().
300        increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));
301        // We need to clear static cache for course_modinfo instances too.
302        course_modinfo::clear_instance_cache();
303        $modinfo = get_fast_modinfo($course->id);
304        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
305        $this->assertGreaterThan($prevcacherev, $cacherev);
306        $cachedvalue = $cache->get($course->id);
307        $this->assertNotEmpty($cachedvalue);
308        $this->assertEquals($cacherev, $cachedvalue->cacherev);
309        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
310        $prevcacherev = $cacherev;
311
312        // Reset cache for all courses and make sure this course cache is reset.
313        rebuild_course_cache(0, true);
314        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
315        $this->assertGreaterThan($prevcacherev, $cacherev);
316        $this->assertEmpty($cache->get($course->id));
317        // Rebuild again.
318        $modinfo = get_fast_modinfo($course->id);
319        $cachedvalue = $cache->get($course->id);
320        $this->assertNotEmpty($cachedvalue);
321        $this->assertEquals($cacherev, $cachedvalue->cacherev);
322        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
323        $prevcacherev = $cacherev;
324
325        // Purge all caches and make sure cacherev is increased and data from MUC erased.
326        purge_all_caches();
327        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
328        $this->assertGreaterThan($prevcacherev, $cacherev);
329        $this->assertEmpty($cache->get($course->id));
330    }
331
332    public function test_course_modinfo_properties() {
333        global $USER, $DB;
334
335        $this->resetAfterTest();
336        $this->setAdminUser();
337
338        // Generate the course and some modules. Make one section hidden.
339        $course = $this->getDataGenerator()->create_course(
340                array('format' => 'topics',
341                    'numsections' => 3),
342                array('createsections' => true));
343        $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',
344                array($course->id, 3));
345        $coursecontext = context_course::instance($course->id);
346        $forum0 = $this->getDataGenerator()->create_module('forum',
347                array('course' => $course->id), array('section' => 0));
348        $assign0 = $this->getDataGenerator()->create_module('assign',
349                array('course' => $course->id), array('section' => 0, 'visible' => 0));
350        $forum1 = $this->getDataGenerator()->create_module('forum',
351                array('course' => $course->id), array('section' => 1));
352        $assign1 = $this->getDataGenerator()->create_module('assign',
353                array('course' => $course->id), array('section' => 1));
354        $page1 = $this->getDataGenerator()->create_module('page',
355                array('course' => $course->id), array('section' => 1));
356        $page3 = $this->getDataGenerator()->create_module('page',
357                array('course' => $course->id), array('section' => 3));
358
359        $modinfo = get_fast_modinfo($course->id);
360
361        $this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid),
362                array_keys($modinfo->cms));
363        $this->assertEquals($course->id, $modinfo->courseid);
364        $this->assertEquals($USER->id, $modinfo->userid);
365        $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
366            1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections);
367        $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances));
368        $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign']));
369        $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum']));
370        $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page']));
371        $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);
372        $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
373            1 => array($forum1->cmid, $assign1->cmid, $page1->cmid),
374            3 => array($page3->cmid)), $modinfo->get_sections());
375        $this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all()));
376        $this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence);
377        $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);
378        $this->assertEquals('', $modinfo->get_section_info(2)->sequence);
379        $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);
380        $this->assertEquals($course->id, $modinfo->get_course()->id);
381        $names = array_keys($modinfo->get_used_module_names());
382        sort($names);
383        $this->assertEquals(array('assign', 'forum', 'page'), $names);
384        $names = array_keys($modinfo->get_used_module_names(true));
385        sort($names);
386        $this->assertEquals(array('assign', 'forum', 'page'), $names);
387        // Admin can see hidden modules/sections.
388        $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);
389        $this->assertTrue($modinfo->get_section_info(3)->uservisible);
390
391        // Get modinfo for non-current user (without capability to view hidden activities/sections).
392        $user = $this->getDataGenerator()->create_user();
393        $modinfo = get_fast_modinfo($course->id, $user->id);
394        $this->assertEquals($user->id, $modinfo->userid);
395        $this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible);
396        $this->assertFalse($modinfo->get_section_info(3)->uservisible);
397
398        // Attempt to access and set non-existing field.
399        $this->assertTrue(empty($modinfo->somefield));
400        $this->assertFalse(isset($modinfo->somefield));
401        $modinfo->somefield;
402        $this->assertDebuggingCalled();
403        $modinfo->somefield = 'Some value';
404        $this->assertDebuggingCalled();
405        $this->assertEmpty($modinfo->somefield);
406        $this->assertDebuggingCalled();
407
408        // Attempt to overwrite existing field.
409        $this->assertFalse(empty($modinfo->cms));
410        $this->assertTrue(isset($modinfo->cms));
411        $modinfo->cms = 'Illegal overwriting';
412        $this->assertDebuggingCalled();
413        $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
414    }
415
416    public function test_is_user_access_restricted_by_capability() {
417        global $DB;
418
419        $this->resetAfterTest();
420
421        // Create a course and a mod_assign instance.
422        $course = $this->getDataGenerator()->create_course();
423        $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
424
425        // Create and enrol a student.
426        $coursecontext = context_course::instance($course->id);
427        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
428        $student = $this->getDataGenerator()->create_user();
429        role_assign($studentrole->id, $student->id, $coursecontext);
430        $enrolplugin = enrol_get_plugin('manual');
431        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
432        $enrolplugin->enrol_user($enrolinstance, $student->id);
433        $this->setUser($student);
434
435        // Make sure student can see the module.
436        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
437        $this->assertTrue($cm->uservisible);
438        $this->assertFalse($cm->is_user_access_restricted_by_capability());
439
440        // Prohibit student to view mod_assign for the course.
441        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);
442        get_fast_modinfo($course->id, 0, true);
443        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
444        $this->assertFalse($cm->uservisible);
445        $this->assertTrue($cm->is_user_access_restricted_by_capability());
446
447        // Restore permission to student to view mod_assign for the course.
448        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);
449        get_fast_modinfo($course->id, 0, true);
450        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
451        $this->assertTrue($cm->uservisible);
452        $this->assertFalse($cm->is_user_access_restricted_by_capability());
453
454        // Prohibit student to view mod_assign for the particular module.
455        role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);
456        get_fast_modinfo($course->id, 0, true);
457        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
458        $this->assertFalse($cm->uservisible);
459        $this->assertTrue($cm->is_user_access_restricted_by_capability());
460
461        // Check calling get_fast_modinfo() for different user:
462        $this->setAdminUser();
463        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
464        $this->assertTrue($cm->uservisible);
465        $this->assertFalse($cm->is_user_access_restricted_by_capability());
466        $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];
467        $this->assertFalse($cm->uservisible);
468        $this->assertTrue($cm->is_user_access_restricted_by_capability());
469    }
470
471    /**
472     * Tests for function cm_info::get_course_module_record()
473     */
474    public function test_cm_info_get_course_module_record() {
475        global $DB;
476
477        $this->resetAfterTest();
478        $this->setAdminUser();
479
480        set_config('enableavailability', 1);
481        set_config('enablecompletion', 1);
482
483        $course = $this->getDataGenerator()->create_course(
484                array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
485                array('createsections' => true));
486        $mods = array();
487        $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
488        $mods[1] = $this->getDataGenerator()->create_module('assign',
489                array('course' => $course->id,
490                    'section' => 3,
491                    'idnumber' => '12345',
492                    'showdescription' => true
493                    ));
494        // Pick a small valid availability value to use.
495        $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
496        $mods[2] = $this->getDataGenerator()->create_module('book',
497                array('course' => $course->id,
498                    'indent' => 5,
499                    'availability' => $availabilityvalue,
500                    'showdescription' => false,
501                    'completion' => true,
502                    'completionview' => true,
503                    'completionexpected' => time() + 5000,
504                    ));
505        $mods[3] = $this->getDataGenerator()->create_module('forum',
506                array('course' => $course->id,
507                    'visible' => 0,
508                    'groupmode' => 1,
509                    'availability' => null));
510        $mods[4] = $this->getDataGenerator()->create_module('forum',
511                array('course' => $course->id,
512                    'grouping' => 12));
513
514        $modinfo = get_fast_modinfo($course->id);
515
516        // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
517        $dbfields = array_keys($DB->get_columns('course_modules'));
518        sort($dbfields);
519        $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
520        $cmrecordfields = array_keys((array)$cmrecord);
521        sort($cmrecordfields);
522        $this->assertEquals($dbfields, $cmrecordfields);
523
524        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
525        // as object returned by get_coursemodule_from_id(,,,true,);
526        $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
527        $cmrecordfullfields = array_keys((array)$cmrecordfull);
528        $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
529        $cmfields = array_keys((array)$cm);
530        $this->assertEquals($cmfields, $cmrecordfullfields);
531
532        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
533        // as object returned by get_coursemodule_from_instance(,,,true,);
534        $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
535        $cmfields = array_keys((array)$cm);
536        $this->assertEquals($cmfields, $cmrecordfullfields);
537
538        // Make sure the objects have the same properties.
539        $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
540        $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
541        $cminfo = $modinfo->get_cm($mods[0]->cmid);
542        $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
543        $this->assertEquals($record, $cminfo->get_course_module_record());
544        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
545        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
546
547        $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
548        $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
549        $cminfo = $modinfo->get_cm($mods[1]->cmid);
550        $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
551        $this->assertEquals($record, $cminfo->get_course_module_record());
552        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
553        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
554
555        $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
556        $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
557        $cminfo = $modinfo->get_cm($mods[2]->cmid);
558        $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
559        $this->assertEquals($record, $cminfo->get_course_module_record());
560        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
561        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
562
563        $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
564        $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
565        $cminfo = $modinfo->get_cm($mods[3]->cmid);
566        $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
567        $this->assertEquals($record, $cminfo->get_course_module_record());
568        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
569        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
570
571        $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
572        $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
573        $cminfo = $modinfo->get_cm($mods[4]->cmid);
574        $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
575        $this->assertEquals($record, $cminfo->get_course_module_record());
576        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
577        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
578
579    }
580
581    /**
582     * Tests the availability property that has been added to course modules
583     * and sections (just to see that it is correctly saved and accessed).
584     */
585    public function test_availability_property() {
586        global $DB, $CFG;
587
588        $this->resetAfterTest();
589
590        // Create a course with two modules and three sections.
591        $course = $this->getDataGenerator()->create_course(
592                array('format' => 'topics', 'numsections' => 3),
593                array('createsections' => true));
594        $forum = $this->getDataGenerator()->create_module('forum',
595                array('course' => $course->id));
596        $forum2 = $this->getDataGenerator()->create_module('forum',
597                array('course' => $course->id));
598
599        // Get modinfo. Check that availability is null for both cm and sections.
600        $modinfo = get_fast_modinfo($course->id);
601        $cm = $modinfo->get_cm($forum->cmid);
602        $this->assertNull($cm->availability);
603        $section = $modinfo->get_section_info(1, MUST_EXIST);
604        $this->assertNull($section->availability);
605
606        // Update availability for cm and section in database.
607        $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));
608        $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));
609
610        // Clear cache and get modinfo again.
611        rebuild_course_cache($course->id, true);
612        get_fast_modinfo(0, 0, true);
613        $modinfo = get_fast_modinfo($course->id);
614
615        // Check values that were changed.
616        $cm = $modinfo->get_cm($forum->cmid);
617        $this->assertEquals('{}', $cm->availability);
618        $section = $modinfo->get_section_info(1, MUST_EXIST);
619        $this->assertEquals('{}', $section->availability);
620
621        // Check other values are still null.
622        $cm = $modinfo->get_cm($forum2->cmid);
623        $this->assertNull($cm->availability);
624        $section = $modinfo->get_section_info(2, MUST_EXIST);
625        $this->assertNull($section->availability);
626    }
627
628    /**
629     * Tests for get_groups() method.
630     */
631    public function test_get_groups() {
632        $this->resetAfterTest();
633        $generator = $this->getDataGenerator();
634
635        // Create courses.
636        $course1 = $generator->create_course();
637        $course2 = $generator->create_course();
638        $course3 = $generator->create_course();
639
640        // Create users.
641        $user1 = $generator->create_user();
642        $user2 = $generator->create_user();
643        $user3 = $generator->create_user();
644
645        // Enrol users on courses.
646        $generator->enrol_user($user1->id, $course1->id);
647        $generator->enrol_user($user2->id, $course2->id);
648        $generator->enrol_user($user3->id, $course2->id);
649        $generator->enrol_user($user3->id, $course3->id);
650
651        // Create groups.
652        $group1 = $generator->create_group(array('courseid' => $course1->id));
653        $group2 = $generator->create_group(array('courseid' => $course2->id));
654        $group3 = $generator->create_group(array('courseid' => $course2->id));
655
656        // Assign users to groups and assert the result.
657        $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));
658        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));
659        $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));
660        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));
661
662        // Create groupings.
663        $grouping1 = $generator->create_grouping(array('courseid' => $course1->id));
664        $grouping2 = $generator->create_grouping(array('courseid' => $course2->id));
665
666        // Assign and assert group to groupings.
667        groups_assign_grouping($grouping1->id, $group1->id);
668        groups_assign_grouping($grouping2->id, $group2->id);
669        groups_assign_grouping($grouping2->id, $group3->id);
670
671        // Test with one single group.
672        $modinfo = get_fast_modinfo($course1, $user1->id);
673        $groups = $modinfo->get_groups($grouping1->id);
674        $this->assertCount(1, $groups);
675        $this->assertArrayHasKey($group1->id, $groups);
676
677        // Test with two groups.
678        $modinfo = get_fast_modinfo($course2, $user2->id);
679        $groups = $modinfo->get_groups();
680        $this->assertCount(2, $groups);
681        $this->assertTrue(in_array($group2->id, $groups));
682        $this->assertTrue(in_array($group3->id, $groups));
683
684        // Test with no groups.
685        $modinfo = get_fast_modinfo($course3, $user3->id);
686        $groups = $modinfo->get_groups();
687        $this->assertCount(0, $groups);
688        $this->assertArrayNotHasKey($group1->id, $groups);
689    }
690
691    /**
692     * Tests the function for constructing a cm_info from mixed data.
693     */
694    public function test_create() {
695        global $CFG, $DB;
696        $this->resetAfterTest();
697
698        // Create a course and an activity.
699        $generator = $this->getDataGenerator();
700        $course = $generator->create_course();
701        $page = $generator->create_module('page', array('course' => $course->id,
702                'name' => 'Annie'));
703
704        // Null is passed through.
705        $this->assertNull(cm_info::create(null));
706
707        // Stdclass object turns into cm_info.
708        $cm = cm_info::create(
709                (object)array('id' => $page->cmid, 'course' => $course->id));
710        $this->assertInstanceOf('cm_info', $cm);
711        $this->assertEquals('Annie', $cm->name);
712
713        // A cm_info object stays as cm_info.
714        $this->assertSame($cm, cm_info::create($cm));
715
716        // Invalid object (missing fields) causes error.
717        try {
718            cm_info::create((object)array('id' => $page->cmid));
719            $this->fail();
720        } catch (Exception $e) {
721            $this->assertInstanceOf('coding_exception', $e);
722        }
723
724        // Create a second hidden activity.
725        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
726                'name' => 'Annie', 'visible' => 0));
727
728        // Create 2 user accounts, one is a manager who can see everything.
729        $user = $generator->create_user();
730        $generator->enrol_user($user->id, $course->id);
731        $manager = $generator->create_user();
732        $generator->enrol_user($manager->id, $course->id,
733                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
734
735        // User can see the normal page but not the hidden one.
736        $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),
737                $user->id);
738        $this->assertTrue($cm->uservisible);
739        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
740                $user->id);
741        $this->assertFalse($cm->uservisible);
742
743        // Manager can see the hidden one too.
744        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
745                $manager->id);
746        $this->assertTrue($cm->uservisible);
747    }
748
749    /**
750     * Tests function for getting $course and $cm at once quickly from modinfo
751     * based on cmid or cm record.
752     */
753    public function test_get_course_and_cm_from_cmid() {
754        global $CFG, $DB;
755        $this->resetAfterTest();
756
757        // Create a course and an activity.
758        $generator = $this->getDataGenerator();
759        $course = $generator->create_course(array('shortname' => 'Halls'));
760        $page = $generator->create_module('page', array('course' => $course->id,
761                'name' => 'Annie'));
762
763        // Successful usage.
764        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);
765        $this->assertEquals('Halls', $course->shortname);
766        $this->assertInstanceOf('cm_info', $cm);
767        $this->assertEquals('Annie', $cm->name);
768
769        // Specified module type.
770        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');
771        $this->assertEquals('Annie', $cm->name);
772
773        // With id in object.
774        $fakecm = (object)array('id' => $page->cmid);
775        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
776        $this->assertEquals('Halls', $course->shortname);
777        $this->assertEquals('Annie', $cm->name);
778
779        // With both id and course in object.
780        $fakecm->course = $course->id;
781        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
782        $this->assertEquals('Halls', $course->shortname);
783        $this->assertEquals('Annie', $cm->name);
784
785        // With supplied course id.
786        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);
787        $this->assertEquals('Annie', $cm->name);
788
789        // With supplied course object (modified just so we can check it is
790        // indeed reusing the supplied object).
791        $course->silly = true;
792        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);
793        $this->assertEquals('Annie', $cm->name);
794        $this->assertTrue($course->silly);
795
796        // Incorrect module type.
797        try {
798            get_course_and_cm_from_cmid($page->cmid, 'forum');
799            $this->fail();
800        } catch (moodle_exception $e) {
801            $this->assertEquals('invalidcoursemodule', $e->errorcode);
802        }
803
804        // Invalid module name.
805        try {
806            get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');
807            $this->fail();
808        } catch (coding_exception $e) {
809            $this->assertContains('Invalid modulename parameter', $e->getMessage());
810        }
811
812        // Doesn't exist.
813        try {
814            get_course_and_cm_from_cmid($page->cmid + 1);
815            $this->fail();
816        } catch (moodle_exception $e) {
817            $this->assertInstanceOf('dml_exception', $e);
818        }
819
820        // Create a second hidden activity.
821        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
822                'name' => 'Annie', 'visible' => 0));
823
824        // Create 2 user accounts, one is a manager who can see everything.
825        $user = $generator->create_user();
826        $generator->enrol_user($user->id, $course->id);
827        $manager = $generator->create_user();
828        $generator->enrol_user($manager->id, $course->id,
829                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
830
831        // User can see the normal page but not the hidden one.
832        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
833        $this->assertTrue($cm->uservisible);
834        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
835        $this->assertFalse($cm->uservisible);
836
837        // Manager can see the hidden one too.
838        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
839        $this->assertTrue($cm->uservisible);
840    }
841
842    /**
843     * Tests function for getting $course and $cm at once quickly from modinfo
844     * based on instance id or record.
845     */
846    public function test_get_course_and_cm_from_instance() {
847        global $CFG, $DB;
848        $this->resetAfterTest();
849
850        // Create a course and an activity.
851        $generator = $this->getDataGenerator();
852        $course = $generator->create_course(array('shortname' => 'Halls'));
853        $page = $generator->create_module('page', array('course' => $course->id,
854                'name' => 'Annie'));
855
856        // Successful usage.
857        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');
858        $this->assertEquals('Halls', $course->shortname);
859        $this->assertInstanceOf('cm_info', $cm);
860        $this->assertEquals('Annie', $cm->name);
861
862        // With id in object.
863        $fakeinstance = (object)array('id' => $page->id);
864        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
865        $this->assertEquals('Halls', $course->shortname);
866        $this->assertEquals('Annie', $cm->name);
867
868        // With both id and course in object.
869        $fakeinstance->course = $course->id;
870        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
871        $this->assertEquals('Halls', $course->shortname);
872        $this->assertEquals('Annie', $cm->name);
873
874        // With supplied course id.
875        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);
876        $this->assertEquals('Annie', $cm->name);
877
878        // With supplied course object (modified just so we can check it is
879        // indeed reusing the supplied object).
880        $course->silly = true;
881        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);
882        $this->assertEquals('Annie', $cm->name);
883        $this->assertTrue($course->silly);
884
885        // Doesn't exist (or is wrong type).
886        try {
887            get_course_and_cm_from_instance($page->id, 'forum');
888            $this->fail();
889        } catch (moodle_exception $e) {
890            $this->assertInstanceOf('dml_exception', $e);
891        }
892
893        // Invalid module name.
894        try {
895            get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');
896            $this->fail();
897        } catch (coding_exception $e) {
898            $this->assertContains('Invalid modulename parameter', $e->getMessage());
899        }
900
901        // Create a second hidden activity.
902        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
903                'name' => 'Annie', 'visible' => 0));
904
905        // Create 2 user accounts, one is a manager who can see everything.
906        $user = $generator->create_user();
907        $generator->enrol_user($user->id, $course->id);
908        $manager = $generator->create_user();
909        $generator->enrol_user($manager->id, $course->id,
910                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
911
912        // User can see the normal page but not the hidden one.
913        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
914        $this->assertTrue($cm->uservisible);
915        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
916        $this->assertFalse($cm->uservisible);
917
918        // Manager can see the hidden one too.
919        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
920        $this->assertTrue($cm->uservisible);
921    }
922}
923