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 * Tests for class core_course_category
19 *
20 * @package    core_course
21 * @category   phpunit
22 * @copyright  2013 Marina Glancy
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28/**
29 * Functional test for class core_course_category
30 */
31class core_course_category_testcase extends advanced_testcase {
32
33    protected $roles;
34
35    protected function setUp() {
36        parent::setUp();
37        $this->resetAfterTest();
38        $user = $this->getDataGenerator()->create_user();
39        $this->setUser($user);
40    }
41
42    protected function get_roleid($context = null) {
43        global $USER;
44        if ($context === null) {
45            $context = context_system::instance();
46        }
47        if (is_object($context)) {
48            $context = $context->id;
49        }
50        if (empty($this->roles)) {
51            $this->roles = array();
52        }
53        if (empty($this->roles[$USER->id])) {
54            $this->roles[$USER->id] = array();
55        }
56        if (empty($this->roles[$USER->id][$context])) {
57            $this->roles[$USER->id][$context] = create_role('Role for '.$USER->id.' in '.$context, 'role'.$USER->id.'-'.$context, '-');
58            role_assign($this->roles[$USER->id][$context], $USER->id, $context);
59        }
60        return $this->roles[$USER->id][$context];
61    }
62
63    protected function assign_capability($capability, $permission = CAP_ALLOW, $contextid = null) {
64        if ($contextid === null) {
65            $contextid = context_system::instance();
66        }
67        if (is_object($contextid)) {
68            $contextid = $contextid->id;
69        }
70        assign_capability($capability, $permission, $this->get_roleid($contextid), $contextid, true);
71        accesslib_clear_all_caches_for_unit_testing();
72    }
73
74    public function test_create_coursecat() {
75        // Create the category.
76        $data = new stdClass();
77        $data->name = 'aaa';
78        $data->description = 'aaa';
79        $data->idnumber = '';
80
81        $category1 = core_course_category::create($data);
82
83        // Initially confirm that base data was inserted correctly.
84        $this->assertSame($data->name, $category1->name);
85        $this->assertSame($data->description, $category1->description);
86        $this->assertSame($data->idnumber, $category1->idnumber);
87
88        $this->assertGreaterThanOrEqual(1, $category1->sortorder);
89
90        // Create two more categories and test the sortorder worked correctly.
91        $data->name = 'ccc';
92        $category2 = core_course_category::create($data);
93
94        $data->name = 'bbb';
95        $category3 = core_course_category::create($data);
96
97        $this->assertGreaterThan($category1->sortorder, $category2->sortorder);
98        $this->assertGreaterThan($category2->sortorder, $category3->sortorder);
99    }
100
101    public function test_name_idnumber_exceptions() {
102        try {
103            core_course_category::create(array('name' => ''));
104            $this->fail('Missing category name exception expected in core_course_category::create');
105        } catch (moodle_exception $e) {
106            $this->assertInstanceOf('moodle_exception', $e);
107        }
108        $cat1 = core_course_category::create(array('name' => 'Cat1', 'idnumber' => '1'));
109        try {
110            $cat1->update(array('name' => ''));
111            $this->fail('Missing category name exception expected in core_course_category::update');
112        } catch (moodle_exception $e) {
113            $this->assertInstanceOf('moodle_exception', $e);
114        }
115        try {
116            core_course_category::create(array('name' => 'Cat2', 'idnumber' => '1'));
117            $this->fail('Duplicate idnumber exception expected in core_course_category::create');
118        } catch (moodle_exception $e) {
119            $this->assertInstanceOf('moodle_exception', $e);
120        }
121        $cat2 = core_course_category::create(array('name' => 'Cat2', 'idnumber' => '2'));
122        try {
123            $cat2->update(array('idnumber' => '1'));
124            $this->fail('Duplicate idnumber exception expected in core_course_category::update');
125        } catch (moodle_exception $e) {
126            $this->assertInstanceOf('moodle_exception', $e);
127        }
128        // Test that duplicates with an idnumber of 0 cannot be created.
129        core_course_category::create(array('name' => 'Cat3', 'idnumber' => '0'));
130        try {
131            core_course_category::create(array('name' => 'Cat4', 'idnumber' => '0'));
132            $this->fail('Duplicate idnumber "0" exception expected in core_course_category::create');
133        } catch (moodle_exception $e) {
134            $this->assertInstanceOf('moodle_exception', $e);
135        }
136        // Test an update cannot make a duplicate idnumber of 0.
137        try {
138            $cat2->update(array('idnumber' => '0'));
139            $this->fail('Duplicate idnumber "0" exception expected in core_course_category::update');
140        } catch (Exception $e) {
141            $this->assertInstanceOf('moodle_exception', $e);
142        }
143    }
144
145    public function test_visibility() {
146        $this->assign_capability('moodle/category:viewhiddencategories');
147        $this->assign_capability('moodle/category:manage');
148
149        // Create category 1 initially hidden.
150        $category1 = core_course_category::create(array('name' => 'Cat1', 'visible' => 0));
151        $this->assertEquals(0, $category1->visible);
152        $this->assertEquals(0, $category1->visibleold);
153
154        // Create category 2 initially hidden as a child of hidden category 1.
155        $category2 = core_course_category::create(array('name' => 'Cat2', 'visible' => 0, 'parent' => $category1->id));
156        $this->assertEquals(0, $category2->visible);
157        $this->assertEquals(0, $category2->visibleold);
158
159        // Create category 3 initially visible as a child of hidden category 1.
160        $category3 = core_course_category::create(array('name' => 'Cat3', 'visible' => 1, 'parent' => $category1->id));
161        $this->assertEquals(0, $category3->visible);
162        $this->assertEquals(1, $category3->visibleold);
163
164        // Show category 1 and make sure that category 2 is hidden and category 3 is visible.
165        $category1->show();
166        $this->assertEquals(1, core_course_category::get($category1->id)->visible);
167        $this->assertEquals(0, core_course_category::get($category2->id)->visible);
168        $this->assertEquals(1, core_course_category::get($category3->id)->visible);
169
170        // Create visible category 4.
171        $category4 = core_course_category::create(array('name' => 'Cat4'));
172        $this->assertEquals(1, $category4->visible);
173        $this->assertEquals(1, $category4->visibleold);
174
175        // Create visible category 5 as a child of visible category 4.
176        $category5 = core_course_category::create(array('name' => 'Cat5', 'parent' => $category4->id));
177        $this->assertEquals(1, $category5->visible);
178        $this->assertEquals(1, $category5->visibleold);
179
180        // Hide category 4 and make sure category 5 is hidden too.
181        $category4->hide();
182        $this->assertEquals(0, $category4->visible);
183        $this->assertEquals(0, $category4->visibleold);
184        $category5 = core_course_category::get($category5->id); // We have to re-read from DB.
185        $this->assertEquals(0, $category5->visible);
186        $this->assertEquals(1, $category5->visibleold);
187
188        // Show category 4 and make sure category 5 is visible too.
189        $category4->show();
190        $this->assertEquals(1, $category4->visible);
191        $this->assertEquals(1, $category4->visibleold);
192        $category5 = core_course_category::get($category5->id); // We have to re-read from DB.
193        $this->assertEquals(1, $category5->visible);
194        $this->assertEquals(1, $category5->visibleold);
195
196        // Move category 5 under hidden category 2 and make sure it became hidden.
197        $category5->change_parent($category2->id);
198        $this->assertEquals(0, $category5->visible);
199        $this->assertEquals(1, $category5->visibleold);
200
201        // Re-read object for category 5 from DB and check again.
202        $category5 = core_course_category::get($category5->id);
203        $this->assertEquals(0, $category5->visible);
204        $this->assertEquals(1, $category5->visibleold);
205
206        // Rricky one! Move hidden category 5 under visible category ("Top") and make sure it is still hidden-
207        // WHY? Well, different people may expect different behaviour here. So better keep it hidden.
208        $category5->change_parent(0);
209        $this->assertEquals(0, $category5->visible);
210        $this->assertEquals(1, $category5->visibleold);
211    }
212
213    public function test_hierarchy() {
214        $this->assign_capability('moodle/category:viewhiddencategories');
215        $this->assign_capability('moodle/category:manage');
216
217        $category1 = core_course_category::create(array('name' => 'Cat1'));
218        $category2 = core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
219        $category3 = core_course_category::create(array('name' => 'Cat3', 'parent' => $category1->id));
220        $category4 = core_course_category::create(array('name' => 'Cat4', 'parent' => $category2->id));
221
222        // Check function get_children().
223        $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
224        // Check function get_parents().
225        $this->assertEquals(array($category1->id, $category2->id), $category4->get_parents());
226
227        // Can not move category to itself or to it's children.
228        $this->assertFalse($category1->can_change_parent($category2->id));
229        $this->assertFalse($category2->can_change_parent($category2->id));
230        // Can move category to grandparent.
231        $this->assertTrue($category4->can_change_parent($category1->id));
232
233        try {
234            $category2->change_parent($category4->id);
235            $this->fail('Exception expected - can not move category');
236        } catch (moodle_exception $e) {
237            $this->assertInstanceOf('moodle_exception', $e);
238        }
239
240        $category4->change_parent(0);
241        $this->assertEquals(array(), $category4->get_parents());
242        $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
243        $this->assertEquals(array(), array_keys($category2->get_children()));
244    }
245
246    public function test_update() {
247        $category1 = core_course_category::create(array('name' => 'Cat1'));
248        $timecreated = $category1->timemodified;
249        $this->assertSame('Cat1', $category1->name);
250        $this->assertTrue(empty($category1->description));
251        $this->waitForSecond();
252        $testdescription = 'This is cat 1 а также русский текст';
253        $category1->update(array('description' => $testdescription));
254        $this->assertSame($testdescription, $category1->description);
255        $category1 = core_course_category::get($category1->id);
256        $this->assertSame($testdescription, $category1->description);
257        cache_helper::purge_by_event('changesincoursecat');
258        $category1 = core_course_category::get($category1->id);
259        $this->assertSame($testdescription, $category1->description);
260
261        $this->assertGreaterThan($timecreated, $category1->timemodified);
262    }
263
264    public function test_delete() {
265        global $DB;
266
267        $this->assign_capability('moodle/category:manage');
268        $this->assign_capability('moodle/course:create');
269
270        $initialcatid = $DB->get_field_sql('SELECT max(id) from {course_categories}');
271
272        $category1 = core_course_category::create(array('name' => 'Cat1'));
273        $category2 = core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
274        $category3 = core_course_category::create(array('name' => 'Cat3'));
275        $category4 = core_course_category::create(array('name' => 'Cat4', 'parent' => $category2->id));
276
277        $course1 = $this->getDataGenerator()->create_course(array('category' => $category2->id));
278        $course2 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
279        $course3 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
280        $course4 = $this->getDataGenerator()->create_course(array('category' => $category1->id));
281
282        // Now we have
283        // $category1
284        //   $category2
285        //      $category4
286        //        $course2
287        //        $course3
288        //      $course1
289        //   $course4
290        // $category3
291        // structure.
292
293        // Login as another user to test course:delete capability (user who created course can delete it within 24h even without cap).
294        $this->setUser($this->getDataGenerator()->create_user());
295
296        // Delete category 2 and move content to category 3.
297        $this->assertFalse($category2->can_move_content_to($category3->id)); // No luck!
298        // Add necessary capabilities.
299        $this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($category3->id));
300        $this->assign_capability('moodle/category:manage');
301        $this->assertTrue($category2->can_move_content_to($category3->id)); // Hurray!
302        $category2->delete_move($category3->id);
303
304        // Make sure we have:
305        // $category1
306        //   $course4
307        // $category3
308        //    $category4
309        //      $course2
310        //      $course3
311        //    $course1
312        // structure.
313
314        $this->assertNull(core_course_category::get($category2->id, IGNORE_MISSING, true));
315        $this->assertEquals(array(), $category1->get_children());
316        $this->assertEquals(array($category4->id), array_keys($category3->get_children()));
317        $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course2->id)));
318        $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course3->id)));
319        $this->assertEquals($category3->id, $DB->get_field('course', 'category', array('id' => $course1->id)));
320
321        // Delete category 3 completely.
322        $this->assertFalse($category3->can_delete_full()); // No luck!
323        // Add necessary capabilities.
324        $this->assign_capability('moodle/course:delete', CAP_ALLOW, context_coursecat::instance($category3->id));
325        $this->assertTrue($category3->can_delete_full()); // Hurray!
326        $category3->delete_full();
327
328        // Make sure we have:
329        // $category1
330        //   $course4
331        // structure.
332
333        // Note that we also have default 'Miscellaneous' category and default 'site' course.
334        $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course_categories} WHERE id > ?', array($initialcatid)));
335        $this->assertEquals($category1->id, $DB->get_field_sql('SELECT max(id) FROM {course_categories}'));
336        $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course} WHERE id <> ?', array(SITEID)));
337        $this->assertEquals(array('id' => $course4->id, 'category' => $category1->id),
338                (array)$DB->get_record_sql('SELECT id, category from {course} where id <> ?', array(SITEID)));
339    }
340
341    public function test_get_children() {
342        $category1 = core_course_category::create(array('name' => 'Cat1'));
343        $category2 = core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
344        $category3 = core_course_category::create(array('name' => 'Cat3', 'parent' => $category1->id, 'visible' => 0));
345        $category4 = core_course_category::create(array('name' => 'Cat4', 'idnumber' => '12', 'parent' => $category1->id));
346        $category5 = core_course_category::create(array('name' => 'Cat5', 'idnumber' => '11',
347            'parent' => $category1->id, 'visible' => 0));
348        $category6 = core_course_category::create(array('name' => 'Cat6', 'idnumber' => '10', 'parent' => $category1->id));
349        $category7 = core_course_category::create(array('name' => 'Cat0', 'parent' => $category1->id));
350
351        $children = $category1->get_children();
352        // User does not have the capability to view hidden categories, so the list should be
353        // 2, 4, 6, 7.
354        $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
355        $this->assertEquals(4, $category1->get_children_count());
356
357        $children = $category1->get_children(array('offset' => 2));
358        $this->assertEquals(array($category6->id, $category7->id), array_keys($children));
359        $this->assertEquals(4, $category1->get_children_count());
360
361        $children = $category1->get_children(array('limit' => 2));
362        $this->assertEquals(array($category2->id, $category4->id), array_keys($children));
363
364        $children = $category1->get_children(array('offset' => 1, 'limit' => 2));
365        $this->assertEquals(array($category4->id, $category6->id), array_keys($children));
366
367        $children = $category1->get_children(array('sort' => array('name' => 1)));
368        // Must be 7, 2, 4, 6.
369        $this->assertEquals(array($category7->id, $category2->id, $category4->id, $category6->id), array_keys($children));
370
371        $children = $category1->get_children(array('sort' => array('idnumber' => 1, 'name' => -1)));
372        // Must be 2, 7, 6, 4.
373        $this->assertEquals(array($category2->id, $category7->id, $category6->id, $category4->id), array_keys($children));
374
375        // Check that everything is all right after purging the caches.
376        cache_helper::purge_by_event('changesincoursecat');
377        $children = $category1->get_children();
378        $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
379        $this->assertEquals(4, $category1->get_children_count());
380    }
381
382    /**
383     * Test the get_all_children_ids function.
384     */
385    public function test_get_all_children_ids() {
386        $category1 = core_course_category::create(array('name' => 'Cat1'));
387        $category2 = core_course_category::create(array('name' => 'Cat2'));
388        $category11 = core_course_category::create(array('name' => 'Cat11', 'parent' => $category1->id));
389        $category12 = core_course_category::create(array('name' => 'Cat12', 'parent' => $category1->id));
390        $category13 = core_course_category::create(array('name' => 'Cat13', 'parent' => $category1->id));
391        $category111 = core_course_category::create(array('name' => 'Cat111', 'parent' => $category11->id));
392        $category112 = core_course_category::create(array('name' => 'Cat112', 'parent' => $category11->id));
393        $category1121 = core_course_category::create(array('name' => 'Cat1121', 'parent' => $category112->id));
394
395        $this->assertCount(0, $category2->get_all_children_ids());
396        $this->assertCount(6, $category1->get_all_children_ids());
397
398        $cmpchildrencat1 = array($category11->id, $category12->id, $category13->id, $category111->id, $category112->id,
399                $category1121->id);
400        $childrencat1 = $category1->get_all_children_ids();
401        // Order of values does not matter. Compare sorted arrays.
402        sort($cmpchildrencat1);
403        sort($childrencat1);
404        $this->assertEquals($cmpchildrencat1, $childrencat1);
405
406        $this->assertCount(3, $category11->get_all_children_ids());
407        $this->assertCount(0, $category111->get_all_children_ids());
408        $this->assertCount(1, $category112->get_all_children_ids());
409
410        $this->assertEquals(array($category1121->id), $category112->get_all_children_ids());
411    }
412
413    /**
414     * Test the countall function
415     */
416    public function test_count_all() {
417        global $DB;
418        // Dont assume there is just one. An add-on might create a category as part of the install.
419        $numcategories = $DB->count_records('course_categories');
420        $this->assertEquals($numcategories, core_course_category::count_all());
421        $this->assertDebuggingCalled('Method core_course_category::count_all() is deprecated. Please use ' .
422            'core_course_category::is_simple_site()', DEBUG_DEVELOPER);
423        $category1 = core_course_category::create(array('name' => 'Cat1'));
424        $category2 = core_course_category::create(array('name' => 'Cat2', 'parent' => $category1->id));
425        $category3 = core_course_category::create(array('name' => 'Cat3', 'parent' => $category2->id, 'visible' => 0));
426        // Now we've got three more.
427        $this->assertEquals($numcategories + 3, core_course_category::count_all());
428        $this->assertDebuggingCalled('Method core_course_category::count_all() is deprecated. Please use ' .
429            'core_course_category::is_simple_site()', DEBUG_DEVELOPER);
430        cache_helper::purge_by_event('changesincoursecat');
431        // We should still have 4.
432        $this->assertEquals($numcategories + 3, core_course_category::count_all());
433        $this->assertDebuggingCalled('Method core_course_category::count_all() is deprecated. Please use ' .
434            'core_course_category::is_simple_site()', DEBUG_DEVELOPER);
435    }
436
437    /**
438     * Test the is_simple_site function
439     */
440    public function test_is_simple_site() {
441        // By default site has one category and is considered simple.
442        $this->assertEquals(true, core_course_category::is_simple_site());
443        $default = core_course_category::get_default();
444        // When there is only one category but it is hidden, it is not a simple site.
445        $default->update(['visible' => 0]);
446        $this->assertEquals(false, core_course_category::is_simple_site());
447        $default->update(['visible' => 1]);
448        $this->assertEquals(true, core_course_category::is_simple_site());
449        // As soon as there is more than one category, site is not simple any more.
450        core_course_category::create(array('name' => 'Cat1'));
451        $this->assertEquals(false, core_course_category::is_simple_site());
452    }
453
454    /**
455     * Test a categories ability to resort courses.
456     */
457    public function test_resort_courses() {
458        $this->resetAfterTest(true);
459        $generator = $this->getDataGenerator();
460        $category = $generator->create_category();
461        $course1 = $generator->create_course(array(
462            'category' => $category->id,
463            'idnumber' => '006-01',
464            'shortname' => 'Biome Study',
465            'fullname' => '<span lang="ar" class="multilang">'.'دراسة منطقة إحيائية'.'</span><span lang="en" class="multilang">Biome Study</span>',
466            'timecreated' => '1000000001'
467        ));
468        $course2 = $generator->create_course(array(
469            'category' => $category->id,
470            'idnumber' => '007-02',
471            'shortname' => 'Chemistry Revision',
472            'fullname' => 'Chemistry Revision',
473            'timecreated' => '1000000002'
474        ));
475        $course3 = $generator->create_course(array(
476            'category' => $category->id,
477            'idnumber' => '007-03',
478            'shortname' => 'Swiss Rolls and Sunflowers',
479            'fullname' => 'Aarkvarks guide to Swiss Rolls and Sunflowers',
480            'timecreated' => '1000000003'
481        ));
482        $course4 = $generator->create_course(array(
483            'category' => $category->id,
484            'idnumber' => '006-04',
485            'shortname' => 'Scratch',
486            'fullname' => '<a href="test.php">Basic Scratch</a>',
487            'timecreated' => '1000000004'
488        ));
489        $c1 = (int)$course1->id;
490        $c2 = (int)$course2->id;
491        $c3 = (int)$course3->id;
492        $c4 = (int)$course4->id;
493
494        $coursecat = core_course_category::get($category->id);
495        $this->assertTrue($coursecat->resort_courses('idnumber'));
496        $this->assertSame(array($c1, $c4, $c2, $c3), array_keys($coursecat->get_courses()));
497
498        $this->assertTrue($coursecat->resort_courses('shortname'));
499        $this->assertSame(array($c1, $c2, $c4, $c3), array_keys($coursecat->get_courses()));
500
501        $this->assertTrue($coursecat->resort_courses('timecreated'));
502        $this->assertSame(array($c1, $c2, $c3, $c4), array_keys($coursecat->get_courses()));
503
504        try {
505            // Enable the multilang filter and set it to apply to headings and content.
506            filter_manager::reset_caches();
507            filter_set_global_state('multilang', TEXTFILTER_ON);
508            filter_set_applies_to_strings('multilang', true);
509            $expected = array($c3, $c4, $c1, $c2);
510        } catch (coding_exception $ex) {
511            $expected = array($c3, $c4, $c2, $c1);
512        }
513        $this->assertTrue($coursecat->resort_courses('fullname'));
514        $this->assertSame($expected, array_keys($coursecat->get_courses()));
515    }
516
517    public function test_get_search_courses() {
518        $cat1 = core_course_category::create(array('name' => 'Cat1'));
519        $cat2 = core_course_category::create(array('name' => 'Cat2', 'parent' => $cat1->id));
520        $c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => ' ', 'idnumber' => 'ID3'));
521        $c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => ' ', 'visible' => 0));
522        $c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test '));
523        $c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => ' ', 'idnumber' => 'ID4'));
524
525        $c5 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => ' '));
526        $c6 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => ' '));
527        $c7 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => ' ', 'visible' => 0));
528        $c8 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => ' '));
529
530        // Get courses in category 1 (returned visible only because user is not enrolled).
531        $res = $cat1->get_courses(array('sortorder' => 1));
532        $this->assertEquals(array($c4->id, $c3->id, $c1->id), array_keys($res)); // Courses are added in reverse order.
533        $this->assertEquals(3, $cat1->get_courses_count());
534
535        // Get courses in category 1 recursively (returned visible only because user is not enrolled).
536        $res = $cat1->get_courses(array('recursive' => 1));
537        $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c6->id, $c5->id), array_keys($res));
538        $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1)));
539
540        // Get courses sorted by fullname.
541        $res = $cat1->get_courses(array('sort' => array('fullname' => 1)));
542        $this->assertEquals(array($c1->id, $c4->id, $c3->id), array_keys($res));
543        $this->assertEquals(3, $cat1->get_courses_count(array('sort' => array('fullname' => 1))));
544
545        // Get courses sorted by fullname recursively.
546        $res = $cat1->get_courses(array('recursive' => 1, 'sort' => array('fullname' => 1)));
547        $this->assertEquals(array($c1->id, $c4->id, $c5->id, $c8->id, $c6->id, $c3->id), array_keys($res));
548        $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'sort' => array('fullname' => 1))));
549
550        // Get courses sorted by fullname recursively, use offset and limit.
551        $res = $cat1->get_courses(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => -1)));
552        $this->assertEquals(array($c6->id, $c8->id), array_keys($res));
553        // Offset and limit do not affect get_courses_count().
554        $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => 1))));
555
556        // Calling get_courses_count without prior call to get_courses().
557        $this->assertEquals(3, $cat2->get_courses_count(array('recursive' => 1, 'sort' => array('idnumber' => 1))));
558
559        // Search courses.
560
561        // Search by text.
562        $res = core_course_category::search_courses(array('search' => 'Test'));
563        $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
564        $this->assertEquals(5, core_course_category::search_courses_count(array('search' => 'Test')));
565
566        // Search by text with specified offset and limit.
567        $options = array('sort' => array('fullname' => 1), 'offset' => 1, 'limit' => 2);
568        $res = core_course_category::search_courses(array('search' => 'Test'), $options);
569        $this->assertEquals(array($c4->id, $c5->id), array_keys($res));
570        $this->assertEquals(5, core_course_category::search_courses_count(array('search' => 'Test'), $options));
571
572        // IMPORTANT: the tests below may fail on some databases
573        // case-insensitive search.
574        $res = core_course_category::search_courses(array('search' => 'test'));
575        $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
576        $this->assertEquals(5, core_course_category::search_courses_count(array('search' => 'test')));
577
578        // Non-latin language search.
579        $res = core_course_category::search_courses(array('search' => 'Математика'));
580        $this->assertEquals(array($c3->id, $c6->id), array_keys($res));
581        $this->assertEquals(2, core_course_category::search_courses_count(array('search' => 'Математика'), array()));
582
583        $this->setUser($this->getDataGenerator()->create_user());
584
585        // Add necessary capabilities.
586        $this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($cat2->id));
587        // Do another search with restricted capabilities.
588        $reqcaps = array('moodle/course:create');
589        $res = core_course_category::search_courses(array('search' => 'test'), array(), $reqcaps);
590        $this->assertEquals(array($c8->id, $c5->id), array_keys($res));
591        $this->assertEquals(2, core_course_category::search_courses_count(array('search' => 'test'), array(), $reqcaps));
592    }
593
594    public function test_course_contacts() {
595        global $DB, $CFG;
596
597        set_config('coursecontactduplicates', false);
598
599        $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'));
600        $managerrole = $DB->get_record('role', array('shortname'=>'manager'));
601        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
602        $oldcoursecontact = $CFG->coursecontact;
603
604        $CFG->coursecontact = $managerrole->id. ','. $teacherrole->id;
605
606        /*
607         * User is listed in course contacts for the course if he has one of the
608         * "course contact" roles ($CFG->coursecontact) AND is enrolled in the course.
609         * If the user has several roles only the highest is displayed.
610         */
611
612        // Test case:
613        //
614        // == Cat1 (user2 has teacher role)
615        //   == Cat2
616        //     -- course21 (user2 is enrolled as manager) | [Expected] Manager: F2 L2
617        //     -- course22 (user2 is enrolled as student) | [Expected] Teacher: F2 L2
618        //     == Cat4 (user2 has manager role)
619        //       -- course41 (user4 is enrolled as teacher, user5 is enrolled as manager) | [Expected] Manager: F5 L5, Teacher: F4 L4
620        //       -- course42 (user2 is enrolled as teacher) | [Expected] Manager: F2 L2
621        //   == Cat3 (user3 has manager role)
622        //     -- course31 (user3 is enrolled as student) | [Expected] Manager: F3 L3
623        //     -- course32                                | [Expected]
624        //   -- course11 (user1 is enrolled as teacher)   | [Expected] Teacher: F1 L1
625        //   -- course12 (user1 has teacher role)         | [Expected]
626        //                also user4 is enrolled as teacher but enrolment is not active
627        $category = $course = $enrol = $user = array();
628        $category[1] = core_course_category::create(array('name' => 'Cat1'))->id;
629        $category[2] = core_course_category::create(array('name' => 'Cat2', 'parent' => $category[1]))->id;
630        $category[3] = core_course_category::create(array('name' => 'Cat3', 'parent' => $category[1]))->id;
631        $category[4] = core_course_category::create(array('name' => 'Cat4', 'parent' => $category[2]))->id;
632        foreach (array(1, 2, 3, 4) as $catid) {
633            foreach (array(1, 2) as $courseid) {
634                $course[$catid][$courseid] = $this->getDataGenerator()->create_course(array('idnumber' => 'id'.$catid.$courseid,
635                    'category' => $category[$catid]))->id;
636                $enrol[$catid][$courseid] = $DB->get_record('enrol', array('courseid'=>$course[$catid][$courseid], 'enrol'=>'manual'), '*', MUST_EXIST);
637            }
638        }
639        foreach (array(1, 2, 3, 4, 5) as $userid) {
640            $user[$userid] = $this->getDataGenerator()->create_user(array('firstname' => 'F'.$userid, 'lastname' => 'L'.$userid))->id;
641        }
642
643        $manual = enrol_get_plugin('manual');
644
645        // Nobody is enrolled now and course contacts are empty.
646        $allcourses = core_course_category::get(0)->get_courses(
647            array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
648        foreach ($allcourses as $onecourse) {
649            $this->assertEmpty($onecourse->get_course_contacts());
650        }
651
652        // Cat1 (user2 has teacher role)
653        role_assign($teacherrole->id, $user[2], context_coursecat::instance($category[1]));
654        // course21 (user2 is enrolled as manager)
655        $manual->enrol_user($enrol[2][1], $user[2], $managerrole->id);
656        // course22 (user2 is enrolled as student)
657        $manual->enrol_user($enrol[2][2], $user[2], $studentrole->id);
658        // Cat4 (user2 has manager role)
659        role_assign($managerrole->id, $user[2], context_coursecat::instance($category[4]));
660        // course41 (user4 is enrolled as teacher, user5 is enrolled as manager)
661        $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id);
662        $manual->enrol_user($enrol[4][1], $user[5], $managerrole->id);
663        // course42 (user2 is enrolled as teacher)
664        $manual->enrol_user($enrol[4][2], $user[2], $teacherrole->id);
665        // Cat3 (user3 has manager role)
666        role_assign($managerrole->id, $user[3], context_coursecat::instance($category[3]));
667        // course31 (user3 is enrolled as student)
668        $manual->enrol_user($enrol[3][1], $user[3], $studentrole->id);
669        // course11 (user1 is enrolled as teacher)
670        $manual->enrol_user($enrol[1][1], $user[1], $teacherrole->id);
671        // -- course12 (user1 has teacher role)
672        //                also user4 is enrolled as teacher but enrolment is not active
673        role_assign($teacherrole->id, $user[1], context_course::instance($course[1][2]));
674        $manual->enrol_user($enrol[1][2], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
675
676        $allcourses = core_course_category::get(0)->get_courses(
677            array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
678        // Simplify the list of contacts for each course (similar as renderer would do).
679        $contacts = array();
680        foreach (array(1, 2, 3, 4) as $catid) {
681            foreach (array(1, 2) as $courseid) {
682                $tmp = array();
683                foreach ($allcourses[$course[$catid][$courseid]]->get_course_contacts() as $contact) {
684                    $tmp[] = $contact['rolename']. ': '. $contact['username'];
685                }
686                $contacts[$catid][$courseid] = join(', ', $tmp);
687            }
688        }
689
690        // Assert:
691        //     -- course21 (user2 is enrolled as manager) | Manager: F2 L2
692        $this->assertSame('Manager: F2 L2', $contacts[2][1]);
693        //     -- course22 (user2 is enrolled as student) | Teacher: F2 L2
694        $this->assertSame('Teacher: F2 L2', $contacts[2][2]);
695        //       -- course41 (user4 is enrolled as teacher, user5 is enrolled as manager) | Manager: F5 L5, Teacher: F4 L4
696        $this->assertSame('Manager: F5 L5, Teacher: F4 L4', $contacts[4][1]);
697        //       -- course42 (user2 is enrolled as teacher) | [Expected] Manager: F2 L2
698        $this->assertSame('Manager: F2 L2', $contacts[4][2]);
699        //     -- course31 (user3 is enrolled as student) | Manager: F3 L3
700        $this->assertSame('Manager: F3 L3', $contacts[3][1]);
701        //     -- course32                                |
702        $this->assertSame('', $contacts[3][2]);
703        //   -- course11 (user1 is enrolled as teacher)   | Teacher: F1 L1
704        $this->assertSame('Teacher: F1 L1', $contacts[1][1]);
705        //   -- course12 (user1 has teacher role)         |
706        $this->assertSame('', $contacts[1][2]);
707
708        // Suspend user 4 and make sure he is no longer in contacts of course 1 in category 4.
709        $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
710        $allcourses = core_course_category::get(0)->get_courses(array(
711                'recursive' => true,
712                'coursecontacts' => true,
713                'sort' => array('idnumber' => 1))
714        );
715        $contacts = $allcourses[$course[4][1]]->get_course_contacts();
716        $this->assertCount(1, $contacts);
717        $contact = reset($contacts);
718        $this->assertEquals('F5 L5', $contact['username']);
719
720        $CFG->coursecontact = $oldcoursecontact;
721    }
722
723    public function test_course_contacts_with_duplicates() {
724        global $DB, $CFG;
725
726        set_config('coursecontactduplicates', true);
727
728        $displayall = get_config('core', 'coursecontactduplicates');
729        $this->assertEquals(true, $displayall);
730
731        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
732        $managerrole = $DB->get_record('role', array('shortname' => 'manager'));
733        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
734        $oldcoursecontact = $CFG->coursecontact;
735
736        $CFG->coursecontact = $managerrole->id. ','. $teacherrole->id;
737
738        /*
739        * User is listed in course contacts for the course if he has one of the
740        * "course contact" roles ($CFG->coursecontact) AND is enrolled in the course.
741        * If the user has several roles all roles are displayed, but each role only once per user.
742        */
743
744        /*
745        * Test case:
746        *
747        * == Cat1 (user2 has teacher role)
748        *    == Cat2
749        *      -- course21 (user2 is enrolled as manager) | [Expected] Manager: F2 L2
750        *      -- course22 (user2 is enrolled as student) | [Expected] Teacher: F2 L2
751        *      == Cat4 (user2 has manager role)
752        *        -- course41 (user4 is enrolled as teacher, user5 is enrolled as manager)
753        *                                                 | [Expected] Manager: F5 L5, Teacher: F4 L4
754        *        -- course42 (user2 is enrolled as teacher) | [Expected] Manager: F2 L2
755        *    == Cat3 (user3 has manager role)
756        *      -- course31 (user3 is enrolled as student) | [Expected] Manager: F3 L3
757        *      -- course32                                | [Expected]
758        *    -- course11 (user1 is enrolled as teacher)   | [Expected] Teacher: F1 L1
759        *    -- course12 (user1 has teacher role)         | [Expected]
760        *                 also user4 is enrolled as teacher but enrolment is not active
761        */
762        $category = $course = $enrol = $user = array();
763        $category[1] = core_course_category::create(array('name' => 'Cat1'))->id;
764        $category[2] = core_course_category::create(array('name' => 'Cat2', 'parent' => $category[1]))->id;
765        $category[3] = core_course_category::create(array('name' => 'Cat3', 'parent' => $category[1]))->id;
766        $category[4] = core_course_category::create(array('name' => 'Cat4', 'parent' => $category[2]))->id;
767        foreach (array(1, 2, 3, 4) as $catid) {
768            foreach (array(1, 2) as $courseid) {
769                $course[$catid][$courseid] = $this->getDataGenerator()->create_course(array(
770                        'idnumber' => 'id'.$catid.$courseid,
771                        'category' => $category[$catid])
772                )->id;
773                $enrol[$catid][$courseid] = $DB->get_record(
774                        'enrol',
775                        array('courseid' => $course[$catid][$courseid], 'enrol' => 'manual'),
776                        '*',
777                        MUST_EXIST
778                );
779            }
780        }
781        foreach (array(1, 2, 3, 4, 5) as $userid) {
782            $user[$userid] = $this->getDataGenerator()->create_user(array(
783                            'firstname' => 'F'.$userid,
784                            'lastname' => 'L'.$userid)
785            )->id;
786        }
787
788        $manual = enrol_get_plugin('manual');
789
790        // Nobody is enrolled now and course contacts are empty.
791        $allcourses = core_course_category::get(0)->get_courses(array(
792                'recursive' => true,
793                'coursecontacts' => true,
794                'sort' => array('idnumber' => 1))
795        );
796        foreach ($allcourses as $onecourse) {
797            $this->assertEmpty($onecourse->get_course_contacts());
798        }
799
800        // Cat1: user2 has teacher role.
801        role_assign($teacherrole->id, $user[2], context_coursecat::instance($category[1]));
802        // Course21: user2 is enrolled as manager.
803        $manual->enrol_user($enrol[2][1], $user[2], $managerrole->id);
804        // Course22: user2 is enrolled as student.
805        $manual->enrol_user($enrol[2][2], $user[2], $studentrole->id);
806        // Cat4: user2 has manager role.
807        role_assign($managerrole->id, $user[2], context_coursecat::instance($category[4]));
808        // Course41: user4 is enrolled as teacher, user5 is enrolled as manager.
809        $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id);
810        $manual->enrol_user($enrol[4][1], $user[5], $managerrole->id);
811        // Course42: user2 is enrolled as teacher.
812        $manual->enrol_user($enrol[4][2], $user[2], $teacherrole->id);
813        // Cat3: user3 has manager role.
814        role_assign($managerrole->id, $user[3], context_coursecat::instance($category[3]));
815        // Course31: user3 is enrolled as student.
816        $manual->enrol_user($enrol[3][1], $user[3], $studentrole->id);
817        // Course11: user1 is enrolled as teacher and user4 is enrolled as teacher and has manager role.
818        $manual->enrol_user($enrol[1][1], $user[1], $teacherrole->id);
819        $manual->enrol_user($enrol[1][1], $user[4], $teacherrole->id);
820        role_assign($managerrole->id, $user[4], context_course::instance($course[1][1]));
821        // Course12: user1 has teacher role, but is not enrolled, as well as user4 is enrolled as teacher, but user4's enrolment is
822        // not active.
823        role_assign($teacherrole->id, $user[1], context_course::instance($course[1][2]));
824        $manual->enrol_user($enrol[1][2], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
825
826        $allcourses = core_course_category::get(0)->get_courses(
827                array('recursive' => true, 'coursecontacts' => true, 'sort' => array('idnumber' => 1)));
828        // Simplify the list of contacts for each course (similar as renderer would do).
829        $contacts = array();
830        foreach (array(1, 2, 3, 4) as $catid) {
831            foreach (array(1, 2) as $courseid) {
832                $tmp = array();
833                foreach ($allcourses[$course[$catid][$courseid]]->get_course_contacts() as $contact) {
834                    $rolenames = array_map(function ($role) {
835                        return $role->displayname;
836                    }, $contact['roles']);
837                    $tmp[] = implode(", ", $rolenames). ': '.
838                            $contact['username'];
839                }
840                $contacts[$catid][$courseid] = join(', ', $tmp);
841            }
842        }
843
844        // Assert:
845        // Course21: user2 is enrolled as manager. [Expected] Manager: F2 L2, Teacher: F2 L2.
846        $this->assertSame('Manager, Teacher: F2 L2', $contacts[2][1]);
847        // Course22: user2 is enrolled as student. [Expected] Teacher: F2 L2.
848        $this->assertSame('Teacher: F2 L2', $contacts[2][2]);
849        // Course41: user4 is enrolled as teacher, user5 is enrolled as manager. [Expected] Manager: F5 L5, Teacher: F4 L4.
850        $this->assertSame('Manager: F5 L5, Teacher: F4 L4', $contacts[4][1]);
851        // Course42: user2 is enrolled as teacher. [Expected] Manager: F2 L2, Teacher: F2 L2.
852        $this->assertSame('Manager, Teacher: F2 L2', $contacts[4][2]);
853        // Course31: user3 is enrolled as student. [Expected] Manager: F3 L3.
854        $this->assertSame('Manager: F3 L3', $contacts[3][1]);
855        // Course32: nobody is enrolled. [Expected] (nothing).
856        $this->assertSame('', $contacts[3][2]);
857        // Course11: user1 is enrolled as teacher and user4 is enrolled as teacher and has manager role. [Expected] Manager: F4 L4,
858        // Teacher: F1 L1, Teacher: F4 L4.
859        $this->assertSame('Manager, Teacher: F4 L4, Teacher: F1 L1', $contacts[1][1]);
860        // Course12: user1 has teacher role, but is not enrolled, as well as user4 is enrolled as teacher, but user4's enrolment is
861        // not active. [Expected] (nothing).
862        $this->assertSame('', $contacts[1][2]);
863
864        // Suspend user 4 and make sure he is no longer in contacts of course 1 in category 4.
865        $manual->enrol_user($enrol[4][1], $user[4], $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED);
866        $allcourses = core_course_category::get(0)->get_courses(array(
867                'recursive' => true,
868                'coursecontacts' => true,
869                'sort' => array('idnumber' => 1)
870        ));
871        $contacts = $allcourses[$course[4][1]]->get_course_contacts();
872        $this->assertCount(1, $contacts);
873        $contact = reset($contacts);
874        $this->assertEquals('F5 L5', $contact['username']);
875
876        $CFG->coursecontact = $oldcoursecontact;
877    }
878
879    public function test_overview_files() {
880        global $CFG;
881        $this->setAdminUser();
882        $cat1 = core_course_category::create(array('name' => 'Cat1'));
883
884        // Create course c1 with one image file.
885        $dratid1 = $this->fill_draft_area(array('filename.jpg' => 'Test file contents1'));
886        $c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
887            'fullname' => 'Test 1', 'overviewfiles_filemanager' => $dratid1));
888        // Create course c2 with two image files (only one file will be added because of settings).
889        $dratid2 = $this->fill_draft_area(array('filename21.jpg' => 'Test file contents21', 'filename22.jpg' => 'Test file contents22'));
890        $c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
891            'fullname' => 'Test 2', 'overviewfiles_filemanager' => $dratid2));
892        // Create course c3 without files.
893        $c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3'));
894
895        // Change the settings to allow multiple files of any types.
896        $CFG->courseoverviewfileslimit = 3;
897        $CFG->courseoverviewfilesext = '*';
898        // Create course c5 with two image files.
899        $dratid4 = $this->fill_draft_area(array('filename41.jpg' => 'Test file contents41', 'filename42.jpg' => 'Test file contents42'));
900        $c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
901            'fullname' => 'Test 4', 'overviewfiles_filemanager' => $dratid4));
902        // Create course c6 with non-image file.
903        $dratid5 = $this->fill_draft_area(array('filename51.zip' => 'Test file contents51'));
904        $c5 = $this->getDataGenerator()->create_course(array('category' => $cat1->id,
905            'fullname' => 'Test 5', 'overviewfiles_filemanager' => $dratid5));
906
907        // Reset default settings.
908        $CFG->courseoverviewfileslimit = 1;
909        $CFG->courseoverviewfilesext = '.jpg,.gif,.png';
910
911        $courses = $cat1->get_courses();
912        $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
913        $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
914        $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
915        $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
916        $this->assertTrue($courses[$c5->id]->has_course_overviewfiles()); // Does not validate the filetypes.
917
918        $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
919        $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles()));
920        $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
921        $this->assertEquals(1, count($courses[$c4->id]->get_course_overviewfiles()));
922        $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles())); // Validate the filetypes.
923
924        // Overview files are not allowed, all functions return empty values.
925        $CFG->courseoverviewfileslimit = 0;
926
927        $this->assertFalse($courses[$c1->id]->has_course_overviewfiles());
928        $this->assertFalse($courses[$c2->id]->has_course_overviewfiles());
929        $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
930        $this->assertFalse($courses[$c4->id]->has_course_overviewfiles());
931        $this->assertFalse($courses[$c5->id]->has_course_overviewfiles());
932
933        $this->assertEquals(0, count($courses[$c1->id]->get_course_overviewfiles()));
934        $this->assertEquals(0, count($courses[$c2->id]->get_course_overviewfiles()));
935        $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
936        $this->assertEquals(0, count($courses[$c4->id]->get_course_overviewfiles()));
937        $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles()));
938
939        // Multiple overview files are allowed but still limited to images.
940        $CFG->courseoverviewfileslimit = 3;
941
942        $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
943        $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
944        $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
945        $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
946        $this->assertTrue($courses[$c5->id]->has_course_overviewfiles()); // Still does not validate the filetypes.
947
948        $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
949        $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles())); // Only 1 file was actually added.
950        $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
951        $this->assertEquals(2, count($courses[$c4->id]->get_course_overviewfiles()));
952        $this->assertEquals(0, count($courses[$c5->id]->get_course_overviewfiles()));
953
954        // Multiple overview files of any type are allowed.
955        $CFG->courseoverviewfilesext = '*';
956
957        $this->assertTrue($courses[$c1->id]->has_course_overviewfiles());
958        $this->assertTrue($courses[$c2->id]->has_course_overviewfiles());
959        $this->assertFalse($courses[$c3->id]->has_course_overviewfiles());
960        $this->assertTrue($courses[$c4->id]->has_course_overviewfiles());
961        $this->assertTrue($courses[$c5->id]->has_course_overviewfiles());
962
963        $this->assertEquals(1, count($courses[$c1->id]->get_course_overviewfiles()));
964        $this->assertEquals(1, count($courses[$c2->id]->get_course_overviewfiles()));
965        $this->assertEquals(0, count($courses[$c3->id]->get_course_overviewfiles()));
966        $this->assertEquals(2, count($courses[$c4->id]->get_course_overviewfiles()));
967        $this->assertEquals(1, count($courses[$c5->id]->get_course_overviewfiles()));
968    }
969
970    public function test_get_nested_name() {
971        $cat1name = 'Cat1';
972        $cat2name = 'Cat2';
973        $cat3name = 'Cat3';
974        $cat4name = 'Cat4';
975        $category1 = core_course_category::create(array('name' => $cat1name));
976        $category2 = core_course_category::create(array('name' => $cat2name, 'parent' => $category1->id));
977        $category3 = core_course_category::create(array('name' => $cat3name, 'parent' => $category2->id));
978        $category4 = core_course_category::create(array('name' => $cat4name, 'parent' => $category2->id));
979
980        $this->assertEquals($cat1name, $category1->get_nested_name(false));
981        $this->assertEquals("{$cat1name} / {$cat2name}", $category2->get_nested_name(false));
982        $this->assertEquals("{$cat1name} / {$cat2name} / {$cat3name}", $category3->get_nested_name(false));
983        $this->assertEquals("{$cat1name} / {$cat2name} / {$cat4name}", $category4->get_nested_name(false));
984    }
985
986    public function test_coursecat_is_uservisible() {
987        global $USER;
988
989        // Create category 1 as visible.
990        $category1 = core_course_category::create(array('name' => 'Cat1', 'visible' => 1));
991        // Create category 2 as hidden.
992        $category2 = core_course_category::create(array('name' => 'Cat2', 'visible' => 0));
993
994        $this->assertTrue($category1->is_uservisible());
995        $this->assertFalse($category2->is_uservisible());
996
997        $this->assign_capability('moodle/category:viewhiddencategories');
998
999        $this->assertTrue($category1->is_uservisible());
1000        $this->assertTrue($category2->is_uservisible());
1001
1002        // First, store current user's id, then login as another user.
1003        $userid = $USER->id;
1004        $this->setUser($this->getDataGenerator()->create_user());
1005
1006        // User $user should still have the moodle/category:viewhiddencategories capability.
1007        $this->assertTrue($category1->is_uservisible($userid));
1008        $this->assertTrue($category2->is_uservisible($userid));
1009
1010        $this->assign_capability('moodle/category:viewhiddencategories', CAP_INHERIT);
1011
1012        $this->assertTrue($category1->is_uservisible());
1013        $this->assertFalse($category2->is_uservisible());
1014    }
1015
1016    public function test_current_user_coursecat_get() {
1017        $this->assign_capability('moodle/category:viewhiddencategories');
1018
1019        // Create category 1 as visible.
1020        $category1 = core_course_category::create(array('name' => 'Cat1', 'visible' => 1));
1021        // Create category 2 as hidden.
1022        $category2 = core_course_category::create(array('name' => 'Cat2', 'visible' => 0));
1023
1024        $this->assertEquals($category1->id, core_course_category::get($category1->id)->id);
1025        $this->assertEquals($category2->id, core_course_category::get($category2->id)->id);
1026
1027        // Login as another user to test core_course_category::get.
1028        $this->setUser($this->getDataGenerator()->create_user());
1029        $this->assertEquals($category1->id, core_course_category::get($category1->id)->id);
1030
1031        // Expecting to get an exception as this new user does not have the moodle/category:viewhiddencategories capability.
1032        $this->expectException('moodle_exception');
1033        $this->expectExceptionMessage(get_string('cannotviewcategory', 'error'));
1034        core_course_category::get($category2->id);
1035    }
1036
1037    public function test_another_user_coursecat_get() {
1038        global $USER;
1039
1040        $this->assign_capability('moodle/category:viewhiddencategories');
1041
1042        // Create category 1 as visible.
1043        $category1 = core_course_category::create(array('name' => 'Cat1', 'visible' => 1));
1044        // Create category 2 as hidden.
1045        $category2 = core_course_category::create(array('name' => 'Cat2', 'visible' => 0));
1046
1047        // First, store current user's object, then login as another user.
1048        $user1 = $USER;
1049        $user2 = $this->getDataGenerator()->create_user();
1050        $this->setUser($user2);
1051
1052        $this->assertEquals($category1->id, core_course_category::get($category1->id, MUST_EXIST, false, $user1)->id);
1053        $this->assertEquals($category2->id, core_course_category::get($category2->id, MUST_EXIST, false, $user1)->id);
1054
1055        $this->setUser($user1);
1056
1057        $this->assertEquals($category1->id, core_course_category::get($category1->id, MUST_EXIST, false, $user2)->id);
1058        $this->expectException('moodle_exception');
1059        $this->expectExceptionMessage(get_string('cannotviewcategory', 'error'));
1060        core_course_category::get($category2->id, MUST_EXIST, false, $user2);
1061    }
1062
1063    /**
1064     * Creates a draft area for current user and fills it with fake files
1065     *
1066     * @param array $files array of files that need to be added to filearea, filename => filecontents
1067     * @return int draftid for the filearea
1068     */
1069    protected function fill_draft_area(array $files) {
1070        global $USER;
1071        $usercontext = context_user::instance($USER->id);
1072        $draftid = file_get_unused_draft_itemid();
1073        foreach ($files as $filename => $filecontents) {
1074            // Add actual file there.
1075            $filerecord = array('component' => 'user', 'filearea' => 'draft',
1076                    'contextid' => $usercontext->id, 'itemid' => $draftid,
1077                    'filename' => $filename, 'filepath' => '/');
1078            $fs = get_file_storage();
1079            $fs->create_file_from_string($filerecord, $filecontents);
1080        }
1081        return $draftid;
1082    }
1083
1084    /**
1085     * This test ensures that is the list of courses in a category can be retrieved while a course is being deleted.
1086     */
1087    public function test_get_courses_during_delete() {
1088        global $DB;
1089        $category = self::getDataGenerator()->create_category();
1090        $course = self::getDataGenerator()->create_course(['category' => $category->id]);
1091        $othercourse = self::getDataGenerator()->create_course(['category' => $category->id]);
1092        $coursecategory = core_course_category::get($category->id);
1093        // Get a list of courses before deletion to populate the cache.
1094        $originalcourses = $coursecategory->get_courses();
1095        $this->assertCount(2, $originalcourses);
1096        $this->assertArrayHasKey($course->id, $originalcourses);
1097        $this->assertArrayHasKey($othercourse->id, $originalcourses);
1098        // Simulate the course deletion process being part way though.
1099        $DB->delete_records('course', ['id' => $course->id]);
1100        // Get the list of courses while a deletion is in progress.
1101        $courses = $coursecategory->get_courses();
1102        $this->assertCount(1, $courses);
1103        $this->assertArrayHasKey($othercourse->id, $courses);
1104    }
1105}
1106