Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Functional test for class core_course_category
  30   */
  31  class 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  }