MDL-59890 coursecat: Add helper to fetch nested view of coursecat
[moodle.git] / lib / tests / coursecatlib_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  * Tests for class coursecat from lib/coursecatlib.php
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  2013 Marina Glancy
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->libdir . '/coursecatlib.php');
31 /**
32  * Functional test for coursecatlib.php
33  */
34 class core_coursecatlib_testcase extends advanced_testcase {
36     protected $roles;
38     protected function setUp() {
39         parent::setUp();
40         $this->resetAfterTest();
41         $user = $this->getDataGenerator()->create_user();
42         $this->setUser($user);
43     }
45     protected function get_roleid($context = null) {
46         global $USER;
47         if ($context === null) {
48             $context = context_system::instance();
49         }
50         if (is_object($context)) {
51             $context = $context->id;
52         }
53         if (empty($this->roles)) {
54             $this->roles = array();
55         }
56         if (empty($this->roles[$USER->id])) {
57             $this->roles[$USER->id] = array();
58         }
59         if (empty($this->roles[$USER->id][$context])) {
60             $this->roles[$USER->id][$context] = create_role('Role for '.$USER->id.' in '.$context, 'role'.$USER->id.'-'.$context, '-');
61             role_assign($this->roles[$USER->id][$context], $USER->id, $context);
62         }
63         return $this->roles[$USER->id][$context];
64     }
66     protected function assign_capability($capability, $permission = CAP_ALLOW, $contextid = null) {
67         if ($contextid === null) {
68             $contextid = context_system::instance();
69         }
70         if (is_object($contextid)) {
71             $contextid = $contextid->id;
72         }
73         assign_capability($capability, $permission, $this->get_roleid($contextid), $contextid, true);
74         accesslib_clear_all_caches_for_unit_testing();
75     }
77     public function test_create_coursecat() {
78         // Create the category.
79         $data = new stdClass();
80         $data->name = 'aaa';
81         $data->description = 'aaa';
82         $data->idnumber = '';
84         $category1 = coursecat::create($data);
86         // Initially confirm that base data was inserted correctly.
87         $this->assertSame($data->name, $category1->name);
88         $this->assertSame($data->description, $category1->description);
89         $this->assertSame($data->idnumber, $category1->idnumber);
91         $this->assertGreaterThanOrEqual(1, $category1->sortorder);
93         // Create two more categories and test the sortorder worked correctly.
94         $data->name = 'ccc';
95         $category2 = coursecat::create($data);
97         $data->name = 'bbb';
98         $category3 = coursecat::create($data);
100         $this->assertGreaterThan($category1->sortorder, $category2->sortorder);
101         $this->assertGreaterThan($category2->sortorder, $category3->sortorder);
102     }
104     public function test_name_idnumber_exceptions() {
105         try {
106             coursecat::create(array('name' => ''));
107             $this->fail('Missing category name exception expected in coursecat::create');
108         } catch (moodle_exception $e) {
109             $this->assertInstanceOf('moodle_exception', $e);
110         }
111         $cat1 = coursecat::create(array('name' => 'Cat1', 'idnumber' => '1'));
112         try {
113             $cat1->update(array('name' => ''));
114             $this->fail('Missing category name exception expected in coursecat::update');
115         } catch (moodle_exception $e) {
116             $this->assertInstanceOf('moodle_exception', $e);
117         }
118         try {
119             coursecat::create(array('name' => 'Cat2', 'idnumber' => '1'));
120             $this->fail('Duplicate idnumber exception expected in coursecat::create');
121         } catch (moodle_exception $e) {
122             $this->assertInstanceOf('moodle_exception', $e);
123         }
124         $cat2 = coursecat::create(array('name' => 'Cat2', 'idnumber' => '2'));
125         try {
126             $cat2->update(array('idnumber' => '1'));
127             $this->fail('Duplicate idnumber exception expected in coursecat::update');
128         } catch (moodle_exception $e) {
129             $this->assertInstanceOf('moodle_exception', $e);
130         }
131         // Test that duplicates with an idnumber of 0 cannot be created.
132         coursecat::create(array('name' => 'Cat3', 'idnumber' => '0'));
133         try {
134             coursecat::create(array('name' => 'Cat4', 'idnumber' => '0'));
135             $this->fail('Duplicate idnumber "0" exception expected in coursecat::create');
136         } catch (moodle_exception $e) {
137             $this->assertInstanceOf('moodle_exception', $e);
138         }
139         // Test an update cannot make a duplicate idnumber of 0.
140         try {
141             $cat2->update(array('idnumber' => '0'));
142             $this->fail('Duplicate idnumber "0" exception expected in coursecat::update');
143         } catch (Exception $e) {
144             $this->assertInstanceOf('moodle_exception', $e);
145         }
146     }
148     public function test_visibility() {
149         $this->assign_capability('moodle/category:viewhiddencategories');
150         $this->assign_capability('moodle/category:manage');
152         // Create category 1 initially hidden.
153         $category1 = coursecat::create(array('name' => 'Cat1', 'visible' => 0));
154         $this->assertEquals(0, $category1->visible);
155         $this->assertEquals(0, $category1->visibleold);
157         // Create category 2 initially hidden as a child of hidden category 1.
158         $category2 = coursecat::create(array('name' => 'Cat2', 'visible' => 0, 'parent' => $category1->id));
159         $this->assertEquals(0, $category2->visible);
160         $this->assertEquals(0, $category2->visibleold);
162         // Create category 3 initially visible as a child of hidden category 1.
163         $category3 = coursecat::create(array('name' => 'Cat3', 'visible' => 1, 'parent' => $category1->id));
164         $this->assertEquals(0, $category3->visible);
165         $this->assertEquals(1, $category3->visibleold);
167         // Show category 1 and make sure that category 2 is hidden and category 3 is visible.
168         $category1->show();
169         $this->assertEquals(1, coursecat::get($category1->id)->visible);
170         $this->assertEquals(0, coursecat::get($category2->id)->visible);
171         $this->assertEquals(1, coursecat::get($category3->id)->visible);
173         // Create visible category 4.
174         $category4 = coursecat::create(array('name' => 'Cat4'));
175         $this->assertEquals(1, $category4->visible);
176         $this->assertEquals(1, $category4->visibleold);
178         // Create visible category 5 as a child of visible category 4.
179         $category5 = coursecat::create(array('name' => 'Cat5', 'parent' => $category4->id));
180         $this->assertEquals(1, $category5->visible);
181         $this->assertEquals(1, $category5->visibleold);
183         // Hide category 4 and make sure category 5 is hidden too.
184         $category4->hide();
185         $this->assertEquals(0, $category4->visible);
186         $this->assertEquals(0, $category4->visibleold);
187         $category5 = coursecat::get($category5->id); // We have to re-read from DB.
188         $this->assertEquals(0, $category5->visible);
189         $this->assertEquals(1, $category5->visibleold);
191         // Show category 4 and make sure category 5 is visible too.
192         $category4->show();
193         $this->assertEquals(1, $category4->visible);
194         $this->assertEquals(1, $category4->visibleold);
195         $category5 = coursecat::get($category5->id); // We have to re-read from DB.
196         $this->assertEquals(1, $category5->visible);
197         $this->assertEquals(1, $category5->visibleold);
199         // Move category 5 under hidden category 2 and make sure it became hidden.
200         $category5->change_parent($category2->id);
201         $this->assertEquals(0, $category5->visible);
202         $this->assertEquals(1, $category5->visibleold);
204         // Re-read object for category 5 from DB and check again.
205         $category5 = coursecat::get($category5->id);
206         $this->assertEquals(0, $category5->visible);
207         $this->assertEquals(1, $category5->visibleold);
209         // Rricky one! Move hidden category 5 under visible category ("Top") and make sure it is still hidden-
210         // WHY? Well, different people may expect different behaviour here. So better keep it hidden.
211         $category5->change_parent(0);
212         $this->assertEquals(0, $category5->visible);
213         $this->assertEquals(1, $category5->visibleold);
214     }
216     public function test_hierarchy() {
217         $this->assign_capability('moodle/category:viewhiddencategories');
218         $this->assign_capability('moodle/category:manage');
220         $category1 = coursecat::create(array('name' => 'Cat1'));
221         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
222         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id));
223         $category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
225         // Check function get_children().
226         $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
227         // Check function get_parents().
228         $this->assertEquals(array($category1->id, $category2->id), $category4->get_parents());
230         // Can not move category to itself or to it's children.
231         $this->assertFalse($category1->can_change_parent($category2->id));
232         $this->assertFalse($category2->can_change_parent($category2->id));
233         // Can move category to grandparent.
234         $this->assertTrue($category4->can_change_parent($category1->id));
236         try {
237             $category2->change_parent($category4->id);
238             $this->fail('Exception expected - can not move category');
239         } catch (moodle_exception $e) {
240             $this->assertInstanceOf('moodle_exception', $e);
241         }
243         $category4->change_parent(0);
244         $this->assertEquals(array(), $category4->get_parents());
245         $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
246         $this->assertEquals(array(), array_keys($category2->get_children()));
247     }
249     public function test_update() {
250         $category1 = coursecat::create(array('name' => 'Cat1'));
251         $timecreated = $category1->timemodified;
252         $this->assertSame('Cat1', $category1->name);
253         $this->assertTrue(empty($category1->description));
254         $this->waitForSecond();
255         $testdescription = 'This is cat 1 а также русский текст';
256         $category1->update(array('description' => $testdescription));
257         $this->assertSame($testdescription, $category1->description);
258         $category1 = coursecat::get($category1->id);
259         $this->assertSame($testdescription, $category1->description);
260         cache_helper::purge_by_event('changesincoursecat');
261         $category1 = coursecat::get($category1->id);
262         $this->assertSame($testdescription, $category1->description);
264         $this->assertGreaterThan($timecreated, $category1->timemodified);
265     }
267     public function test_delete() {
268         global $DB;
270         $this->assign_capability('moodle/category:manage');
271         $this->assign_capability('moodle/course:create');
273         $initialcatid = $DB->get_field_sql('SELECT max(id) from {course_categories}');
275         $category1 = coursecat::create(array('name' => 'Cat1'));
276         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
277         $category3 = coursecat::create(array('name' => 'Cat3'));
278         $category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
280         $course1 = $this->getDataGenerator()->create_course(array('category' => $category2->id));
281         $course2 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
282         $course3 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
283         $course4 = $this->getDataGenerator()->create_course(array('category' => $category1->id));
285         // Now we have
286         // $category1
287         //   $category2
288         //      $category4
289         //        $course2
290         //        $course3
291         //      $course1
292         //   $course4
293         // $category3
294         // structure.
296         // Login as another user to test course:delete capability (user who created course can delete it within 24h even without cap).
297         $this->setUser($this->getDataGenerator()->create_user());
299         // Delete category 2 and move content to category 3.
300         $this->assertFalse($category2->can_move_content_to($category3->id)); // No luck!
301         // Add necessary capabilities.
302         $this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($category3->id));
303         $this->assign_capability('moodle/category:manage');
304         $this->assertTrue($category2->can_move_content_to($category3->id)); // Hurray!
305         $category2->delete_move($category3->id);
307         // Make sure we have:
308         // $category1
309         //   $course4
310         // $category3
311         //    $category4
312         //      $course2
313         //      $course3
314         //    $course1
315         // structure.
317         $this->assertNull(coursecat::get($category2->id, IGNORE_MISSING, true));
318         $this->assertEquals(array(), $category1->get_children());
319         $this->assertEquals(array($category4->id), array_keys($category3->get_children()));
320         $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course2->id)));
321         $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course3->id)));
322         $this->assertEquals($category3->id, $DB->get_field('course', 'category', array('id' => $course1->id)));
324         // Delete category 3 completely.
325         $this->assertFalse($category3->can_delete_full()); // No luck!
326         // Add necessary capabilities.
327         $this->assign_capability('moodle/course:delete', CAP_ALLOW, context_coursecat::instance($category3->id));
328         $this->assertTrue($category3->can_delete_full()); // Hurray!
329         $category3->delete_full();
331         // Make sure we have:
332         // $category1
333         //   $course4
334         // structure.
336         // Note that we also have default 'Miscellaneous' category and default 'site' course.
337         $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course_categories} WHERE id > ?', array($initialcatid)));
338         $this->assertEquals($category1->id, $DB->get_field_sql('SELECT max(id) FROM {course_categories}'));
339         $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course} WHERE id <> ?', array(SITEID)));
340         $this->assertEquals(array('id' => $course4->id, 'category' => $category1->id),
341                 (array)$DB->get_record_sql('SELECT id, category from {course} where id <> ?', array(SITEID)));
342     }
344     public function test_get_children() {
345         $category1 = coursecat::create(array('name' => 'Cat1'));
346         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
347         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id, 'visible' => 0));
348         $category4 = coursecat::create(array('name' => 'Cat4', 'idnumber' => '12', 'parent' => $category1->id));
349         $category5 = coursecat::create(array('name' => 'Cat5', 'idnumber' => '11', 'parent' => $category1->id, 'visible' => 0));
350         $category6 = coursecat::create(array('name' => 'Cat6', 'idnumber' => '10', 'parent' => $category1->id));
351         $category7 = coursecat::create(array('name' => 'Cat0', 'parent' => $category1->id));
353         $children = $category1->get_children();
354         // User does not have the capability to view hidden categories, so the list should be
355         // 2, 4, 6, 7.
356         $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
357         $this->assertEquals(4, $category1->get_children_count());
359         $children = $category1->get_children(array('offset' => 2));
360         $this->assertEquals(array($category6->id, $category7->id), array_keys($children));
361         $this->assertEquals(4, $category1->get_children_count());
363         $children = $category1->get_children(array('limit' => 2));
364         $this->assertEquals(array($category2->id, $category4->id), array_keys($children));
366         $children = $category1->get_children(array('offset' => 1, 'limit' => 2));
367         $this->assertEquals(array($category4->id, $category6->id), array_keys($children));
369         $children = $category1->get_children(array('sort' => array('name' => 1)));
370         // Must be 7, 2, 4, 6.
371         $this->assertEquals(array($category7->id, $category2->id, $category4->id, $category6->id), array_keys($children));
373         $children = $category1->get_children(array('sort' => array('idnumber' => 1, 'name' => -1)));
374         // Must be 2, 7, 6, 4.
375         $this->assertEquals(array($category2->id, $category7->id, $category6->id, $category4->id), array_keys($children));
377         // Check that everything is all right after purging the caches.
378         cache_helper::purge_by_event('changesincoursecat');
379         $children = $category1->get_children();
380         $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
381         $this->assertEquals(4, $category1->get_children_count());
382     }
384     /**
385      * Test the countall function
386      */
387     public function test_count_all() {
388         global $DB;
389         // Dont assume there is just one. An add-on might create a category as part of the install.
390         $numcategories = $DB->count_records('course_categories');
391         $this->assertEquals($numcategories, coursecat::count_all());
392         $category1 = coursecat::create(array('name' => 'Cat1'));
393         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
394         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category2->id, 'visible' => 0));
395         // Now we've got three more.
396         $this->assertEquals($numcategories + 3, coursecat::count_all());
397         cache_helper::purge_by_event('changesincoursecat');
398         // We should still have 4.
399         $this->assertEquals($numcategories + 3, coursecat::count_all());
400     }
402     /**
403      * Test a categories ability to resort courses.
404      */
405     public function test_resort_courses() {
406         $this->resetAfterTest(true);
407         $generator = $this->getDataGenerator();
408         $category = $generator->create_category();
409         $course1 = $generator->create_course(array(
410             'category' => $category->id,
411             'idnumber' => '006-01',
412             'shortname' => 'Biome Study',
413             'fullname' => '<span lang="ar" class="multilang">'.'دراسة منطقة إحيائية'.'</span><span lang="en" class="multilang">Biome Study</span>',
414             'timecreated' => '1000000001'
415         ));
416         $course2 = $generator->create_course(array(
417             'category' => $category->id,
418             'idnumber' => '007-02',
419             'shortname' => 'Chemistry Revision',
420             'fullname' => 'Chemistry Revision',
421             'timecreated' => '1000000002'
422         ));
423         $course3 = $generator->create_course(array(
424             'category' => $category->id,
425             'idnumber' => '007-03',
426             'shortname' => 'Swiss Rolls and Sunflowers',
427             'fullname' => 'Aarkvarks guide to Swiss Rolls and Sunflowers',
428             'timecreated' => '1000000003'
429         ));
430         $course4 = $generator->create_course(array(
431             'category' => $category->id,
432             'idnumber' => '006-04',
433             'shortname' => 'Scratch',
434             'fullname' => '<a href="test.php">Basic Scratch</a>',
435             'timecreated' => '1000000004'
436         ));
437         $c1 = (int)$course1->id;
438         $c2 = (int)$course2->id;
439         $c3 = (int)$course3->id;
440         $c4 = (int)$course4->id;
442         $coursecat = coursecat::get($category->id);
443         $this->assertTrue($coursecat->resort_courses('idnumber'));
444         $this->assertSame(array($c1, $c4, $c2, $c3), array_keys($coursecat->get_courses()));
446         $this->assertTrue($coursecat->resort_courses('shortname'));
447         $this->assertSame(array($c1, $c2, $c4, $c3), array_keys($coursecat->get_courses()));
449         $this->assertTrue($coursecat->resort_courses('timecreated'));
450         $this->assertSame(array($c1, $c2, $c3, $c4), array_keys($coursecat->get_courses()));
452         try {
453             // Enable the multilang filter and set it to apply to headings and content.
454             filter_manager::reset_caches();
455             filter_set_global_state('multilang', TEXTFILTER_ON);
456             filter_set_applies_to_strings('multilang', true);
457             $expected = array($c3, $c4, $c1, $c2);
458         } catch (coding_exception $ex) {
459             $expected = array($c3, $c4, $c2, $c1);
460         }
461         $this->assertTrue($coursecat->resort_courses('fullname'));
462         $this->assertSame($expected, array_keys($coursecat->get_courses()));
463     }
465     public function test_get_search_courses() {
466         $cat1 = coursecat::create(array('name' => 'Cat1'));
467         $cat2 = coursecat::create(array('name' => 'Cat2', 'parent' => $cat1->id));
468         $c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => ' ', 'idnumber' => 'ID3'));
469         $c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => ' ', 'visible' => 0));
470         $c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test '));
471         $c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => ' ', 'idnumber' => 'ID4'));
473         $c5 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => ' '));
474         $c6 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => ' '));
475         $c7 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => ' ', 'visible' => 0));
476         $c8 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => ' '));
478         // Get courses in category 1 (returned visible only because user is not enrolled).
479         $res = $cat1->get_courses(array('sortorder' => 1));
480         $this->assertEquals(array($c4->id, $c3->id, $c1->id), array_keys($res)); // Courses are added in reverse order.
481         $this->assertEquals(3, $cat1->get_courses_count());
483         // Get courses in category 1 recursively (returned visible only because user is not enrolled).
484         $res = $cat1->get_courses(array('recursive' => 1));
485         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c6->id, $c5->id), array_keys($res));
486         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1)));
488         // Get courses sorted by fullname.
489         $res = $cat1->get_courses(array('sort' => array('fullname' => 1)));
490         $this->assertEquals(array($c1->id, $c4->id, $c3->id), array_keys($res));
491         $this->assertEquals(3, $cat1->get_courses_count(array('sort' => array('fullname' => 1))));
493         // Get courses sorted by fullname recursively.
494         $res = $cat1->get_courses(array('recursive' => 1, 'sort' => array('fullname' => 1)));
495         $this->assertEquals(array($c1->id, $c4->id, $c5->id, $c8->id, $c6->id, $c3->id), array_keys($res));
496         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'sort' => array('fullname' => 1))));
498         // Get courses sorted by fullname recursively, use offset and limit.
499         $res = $cat1->get_courses(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => -1)));
500         $this->assertEquals(array($c6->id, $c8->id), array_keys($res));
501         // Offset and limit do not affect get_courses_count().
502         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => 1))));
504         // Calling get_courses_count without prior call to get_courses().
505         $this->assertEquals(3, $cat2->get_courses_count(array('recursive' => 1, 'sort' => array('idnumber' => 1))));
507         // Search courses.
509         // Search by text.
510         $res = coursecat::search_courses(array('search' => 'Test'));
511         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
512         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'Test')));
514         // Search by text with specified offset and limit.
515         $options = array('sort' => array('fullname' => 1), 'offset' => 1, 'limit' => 2);
516         $res = coursecat::search_courses(array('search' => 'Test'), $options);
517         $this->assertEquals(array($c4->id, $c5->id), array_keys($res));
518         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'Test'), $options));
520         // IMPORTANT: the tests below may fail on some databases
521         // case-insensitive search.
522         $res = coursecat::search_courses(array('search' => 'test'));
523         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
524         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'test')));
526         // Non-latin language search.
527         $res = coursecat::search_courses(array('search' => 'Математика'));
528         $this->assertEquals(array($c3->id, $c6->id), array_keys($res));
529         $this->assertEquals(2, coursecat::search_courses_count(array('search' => 'Математика'), array()));
531         $this->setUser($this->getDataGenerator()->create_user());
533         // Add necessary capabilities.
534         $this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($cat2->id));
535         // Do another search with restricted capabilities.
536         $reqcaps = array('moodle/course:create');
537         $res = coursecat::search_courses(array('search' => 'test'), array(), $reqcaps);
538         $this->assertEquals(array($c8->id, $c5->id), array_keys($res));
539         $this->assertEquals(2, coursecat::search_courses_count(array('search' => 'test'), array(), $reqcaps));
540     }
542     public function test_course_contacts() {
543         global $DB, $CFG;
544         $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
545         $managerrole = $DB->get_record('role', array('shortname'=>'manager'));
546         $studentrole = $DB->get_record('role', array('shortname'=>'student'));
547         $oldcoursecontact = $CFG->coursecontact;
549         $CFG->coursecontact = $managerrole->id. ','. $teacherrole->id;
551         /*
552          * User is listed in course contacts for the course if he has one of the
553          * "course contact" roles ($CFG->coursecontact) AND is enrolled in the course.
554          * If the user has several roles only the highest is displayed.
555          */
557         // Test case:
558         //
559         // == Cat1 (user2 has teacher role)
560         //   == Cat2
561         //     -- course21 (user2 is enrolled as manager) | [Expected] Manager: F2 L2
562         //     -- course22 (user2 is enrolled as student) | [Expected] Teacher: F2 L2
563         //     == Cat4 (user2 has manager role)
564         //       -- course41 (user4 is enrolled as teacher, user5 is enrolled as manager) | [Expected] Manager: F5 L5, Teacher: F4 L4
565         //       -- course42 (user2 is enrolled as teacher) | [Expected] Manager: F2 L2
566         //   == Cat3 (user3 has manager role)
567         //     -- course31 (user3 is enrolled as student) | [Expected] Manager: F3 L3
568         //     -- course32                                | [Expected]
569         //   -- course11 (user1 is enrolled as teacher)   | [Expected] Teacher: F1 L1
570         //   -- course12 (user1 has teacher role)         | [Expected]
571         //                also user4 is enrolled as teacher but enrolment is not active
572         $category = $course = $enrol = $user = array();
573         $category[1] = coursecat::create(array('name' => 'Cat1'))->id;
574         $category[2] = coursecat::create(array('name' => 'Cat2', 'parent' => $category[1]))->id;
575         $category[3] = coursecat::create(array('name' => 'Cat3', 'parent' => $category[1]))->id;
576         $category[4] = coursecat::create(array('name' => 'Cat4', 'parent' => $category[2]))->id;
577         foreach (array(1, 2, 3, 4) as $catid) {
578             foreach (array(1, 2) as $courseid) {
579                 $course[$catid][$courseid] = $this->getDataGenerator()->create_course(array('idnumber' => 'id'.$catid.$courseid,
580                     'category' => $category[$catid]))->id;
581                 $enrol[$catid][$courseid] = $DB->get_record('enrol', array('courseid'=>$course[$catid][$courseid], 'enrol'=>'manual'), '*', MUST_EXIST);
582             }
583         }
584         foreach (array(1, 2, 3, 4, 5) as $userid) {
585             $user[$userid] = $this->getDataGenerator()->create_user(array('firstname' => 'F'.$userid, 'lastname' => 'L'.$userid))->id;
586         }
588         $manual = enrol_get_plugin('manual');
590         // Nobody is enrolled now and course contacts are empty.
591         $allcourses = coursecat::get(0)->get_courses(array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
592         foreach ($allcourses as $onecourse) {
593             $this->assertEmpty($onecourse->get_course_contacts());
594         }
596         // Cat1 (user2 has teacher role)
597         role_assign($teacherrole->id, $user[2], context_coursecat::instance($category[1]));
598         // course21 (user2 is enrolled as manager)
599         $manual->enrol_user($enrol[2][1], $user[2], $managerrole->id);
600         // course22 (user2 is enrolled as student)
601         $manual->enrol_user($enrol[2][2], $user[2], $studentrole->id);
602         // Cat4 (user2 has manager role)
603         role_assign($managerrole->id, $user[2], context_coursecat::instance($category[4]));
604         // course41 (user4 is enrolled as teacher, user5 is enrolled as manager)
605         $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id);
606         $manual->enrol_user($enrol[4][1], $user[5], $managerrole->id);
607         // course42 (user2 is enrolled as teacher)
608         $manual->enrol_user($enrol[4][2], $user[2], $teacherrole->id);
609         // Cat3 (user3 has manager role)
610         role_assign($managerrole->id, $user[3], context_coursecat::instance($category[3]));
611         // course31 (user3 is enrolled as student)
612         $manual->enrol_user($enrol[3][1], $user[3], $studentrole->id);
613         // course11 (user1 is enrolled as teacher)
614         $manual->enrol_user($enrol[1][1], $user[1], $teacherrole->id);
615         // -- course12 (user1 has teacher role)
616         //                also user4 is enrolled as teacher but enrolment is not active
617         role_assign($teacherrole->id, $user[1], context_course::instance($course[1][2]));
618         $manual->enrol_user($enrol[1][2], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
620         $allcourses = coursecat::get(0)->get_courses(array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
621         // Simplify the list of contacts for each course (similar as renderer would do).
622         $contacts = array();
623         foreach (array(1, 2, 3, 4) as $catid) {
624             foreach (array(1, 2) as $courseid) {
625                 $tmp = array();
626                 foreach ($allcourses[$course[$catid][$courseid]]->get_course_contacts() as $contact) {
627                     $tmp[] = $contact['rolename']. ': '. $contact['username'];
628                 }
629                 $contacts[$catid][$courseid] = join(', ', $tmp);
630             }
631         }
633         // Assert:
634         //     -- course21 (user2 is enrolled as manager) | Manager: F2 L2
635         $this->assertSame('Manager: F2 L2', $contacts[2][1]);
636         //     -- course22 (user2 is enrolled as student) | Teacher: F2 L2
637         $this->assertSame('Teacher: F2 L2', $contacts[2][2]);
638         //       -- course41 (user4 is enrolled as teacher, user5 is enrolled as manager) | Manager: F5 L5, Teacher: F4 L4
639         $this->assertSame('Manager: F5 L5, Teacher: F4 L4', $contacts[4][1]);
640         //       -- course42 (user2 is enrolled as teacher) | [Expected] Manager: F2 L2
641         $this->assertSame('Manager: F2 L2', $contacts[4][2]);
642         //     -- course31 (user3 is enrolled as student) | Manager: F3 L3
643         $this->assertSame('Manager: F3 L3', $contacts[3][1]);
644         //     -- course32                                |
645         $this->assertSame('', $contacts[3][2]);
646         //   -- course11 (user1 is enrolled as teacher)   | Teacher: F1 L1
647         $this->assertSame('Teacher: F1 L1', $contacts[1][1]);
648         //   -- course12 (user1 has teacher role)         |
649         $this->assertSame('', $contacts[1][2]);
651         // Suspend user 4 and make sure he is no longer in contacts of course 1 in category 4.
652         $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
653         $allcourses = coursecat::get(0)->get_courses(array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
654         $contacts = $allcourses[$course[4][1]]->get_course_contacts();
655         $this->assertCount(1, $contacts);
656         $contact = reset($contacts);
657         $this->assertEquals('F5 L5', $contact['username']);
659         $CFG->coursecontact = $oldcoursecontact;
660     }
662     public function test_overview_files() {
663         global $CFG;
664         $this->setAdminUser();
665         $cat1 = coursecat::create(array('name' => 'Cat1'));
667         // Create course c1 with one image file.
668         $dratid1 = $this->fill_draft_area(array('filename.jpg' => 'Test file contents1'));
669         $c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
670             'fullname' => 'Test 1', 'overviewfiles_filemanager' => $dratid1));
671         // Create course c2 with two image files (only one file will be added because of settings).
672         $dratid2 = $this->fill_draft_area(array('filename21.jpg' => 'Test file contents21', 'filename22.jpg' => 'Test file contents22'));
673         $c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
674             'fullname' => 'Test 2', 'overviewfiles_filemanager' => $dratid2));
675         // Create course c3 without files.
676         $c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3'));
678         // Change the settings to allow multiple files of any types.
679         $CFG->courseoverviewfileslimit = 3;
680         $CFG->courseoverviewfilesext = '*';
681         // Create course c5 with two image files.
682         $dratid4 = $this->fill_draft_area(array('filename41.jpg' => 'Test file contents41', 'filename42.jpg' => 'Test file contents42'));
683         $c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
684             'fullname' => 'Test 4', 'overviewfiles_filemanager' => $dratid4));
685         // Create course c6 with non-image file.
686         $dratid5 = $this->fill_draft_area(array('filename51.zip' => 'Test file contents51'));
687         $c5 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
688             'fullname' => 'Test 5', 'overviewfiles_filemanager' => $dratid5));
690         // Reset default settings.
691         $CFG->courseoverviewfileslimit = 1;
692         $CFG->courseoverviewfilesext = '.jpg,.gif,.png';
694         $courses = $cat1->get_courses();
695         $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
696         $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
697         $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
698         $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
699         $this->assertTrue($courses[$c5->id]->has_course_overviewfiles()); // Does not validate the filetypes.
701         $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
702         $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles()));
703         $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
704         $this->assertEquals(1, count($courses[$c4->id]->get_course_overviewfiles()));
705         $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles())); // Validate the filetypes.
707         // Overview files are not allowed, all functions return empty values.
708         $CFG->courseoverviewfileslimit = 0;
710         $this->assertFalse($courses[$c1->id]->has_course_overviewfiles());
711         $this->assertFalse($courses[$c2->id]->has_course_overviewfiles());
712         $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
713         $this->assertFalse($courses[$c4->id]->has_course_overviewfiles());
714         $this->assertFalse($courses[$c5->id]->has_course_overviewfiles());
716         $this->assertEquals(0, count($courses[$c1->id]->get_course_overviewfiles()));
717         $this->assertEquals(0, count($courses[$c2->id]->get_course_overviewfiles()));
718         $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
719         $this->assertEquals(0, count($courses[$c4->id]->get_course_overviewfiles()));
720         $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles()));
722         // Multiple overview files are allowed but still limited to images.
723         $CFG->courseoverviewfileslimit = 3;
725         $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
726         $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
727         $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
728         $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
729         $this->assertTrue($courses[$c5->id]->has_course_overviewfiles()); // Still does not validate the filetypes.
731         $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
732         $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles())); // Only 1 file was actually added.
733         $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
734         $this->assertEquals(2, count($courses[$c4->id]->get_course_overviewfiles()));
735         $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles()));
737         // Multiple overview files of any type are allowed.
738         $CFG->courseoverviewfilesext = '*';
740         $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
741         $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
742         $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
743         $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
744         $this->assertTrue($courses[$c5->id]->has_course_overviewfiles());
746         $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
747         $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles()));
748         $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
749         $this->assertEquals(2, count($courses[$c4->id]->get_course_overviewfiles()));
750         $this->assertEquals(1, count($courses[$c5->id]->get_course_overviewfiles()));
751     }
753     public function test_get_nested_name() {
754         $cat1name = 'Cat1';
755         $cat2name = 'Cat2';
756         $cat3name = 'Cat3';
757         $cat4name = 'Cat4';
758         $category1 = coursecat::create(array('name' => $cat1name));
759         $category2 = coursecat::create(array('name' => $cat2name, 'parent' => $category1->id));
760         $category3 = coursecat::create(array('name' => $cat3name, 'parent' => $category2->id));
761         $category4 = coursecat::create(array('name' => $cat4name, 'parent' => $category2->id));
763         $this->assertEquals($cat1name, $category1->get_nested_name(false));
764         $this->assertEquals("{$cat1name} / {$cat2name}", $category2->get_nested_name(false));
765         $this->assertEquals("{$cat1name} / {$cat2name} / {$cat3name}", $category3->get_nested_name(false));
766         $this->assertEquals("{$cat1name} / {$cat2name} / {$cat4name}", $category4->get_nested_name(false));
767     }
769     /**
770      * Creates a draft area for current user and fills it with fake files
771      *
772      * @param array $files array of files that need to be added to filearea, filename => filecontents
773      * @return int draftid for the filearea
774      */
775     protected function fill_draft_area(array $files) {
776         global $USER;
777         $usercontext = context_user::instance($USER->id);
778         $draftid = file_get_unused_draft_itemid();
779         foreach ($files as $filename => $filecontents) {
780             // Add actual file there.
781             $filerecord = array('component' => 'user', 'filearea' => 'draft',
782                     'contextid' => $usercontext->id, 'itemid' => $draftid,
783                     'filename' => $filename, 'filepath' => '/');
784             $fs = get_file_storage();
785             $fs->create_file_from_string($filerecord, $filecontents);
786         }
787         return $draftid;
788     }