Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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(): void {
      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  }