Search moodle.org's
Developer Documentation

See Release Notes

  • 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 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [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  namespace core_course;
  18  
  19  use core_course_category;
  20  use core_course_list_element;
  21  use course_capability_assignment;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot.'/course/lib.php');
  27  require_once($CFG->dirroot.'/course/tests/fixtures/course_capability_assignment.php');
  28  
  29  /**
  30   * Course and category management helper class tests.
  31   *
  32   * @package    core_course
  33   * @copyright  2013 Sam Hemelryk
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class management_helper_test extends \advanced_testcase {
  37  
  38      /** Category management capability: moodle/category:manage */
  39      const CATEGORY_MANAGE = 'moodle/category:manage';
  40      /** View hidden category capability: moodle/category:viewhiddencategories */
  41      const CATEGORY_VIEWHIDDEN = 'moodle/category:viewhiddencategories';
  42      /** View course capability: moodle/course:visibility */
  43      const COURSE_VIEW = 'moodle/course:visibility';
  44      /** View hidden course capability: moodle/course:viewhiddencourses */
  45      const COURSE_VIEWHIDDEN = 'moodle/course:viewhiddencourses';
  46  
  47      /**
  48       * Returns a user object and its assigned new role.
  49       *
  50       * @param testing_data_generator $generator
  51       * @param $contextid
  52       * @return array The user object and the role ID
  53       */
  54      protected function get_user_objects(\testing_data_generator $generator, $contextid) {
  55          global $USER;
  56  
  57          if (empty($USER->id)) {
  58              $user  = $generator->create_user();
  59              $this->setUser($user);
  60          }
  61          $roleid = create_role('Test role', 'testrole', 'Test role description');
  62          if (!is_array($contextid)) {
  63              $contextid = array($contextid);
  64          }
  65          foreach ($contextid as $cid) {
  66              $assignid = role_assign($roleid, $user->id, $cid);
  67          }
  68          return array($user, $roleid);
  69      }
  70  
  71      /**
  72       * Tests:
  73       *   - action_category_hide
  74       *   - action_category_show
  75       *
  76       * In order to show/hide the user must have moodle/category:manage on the parent context.
  77       * In order to view hidden categories the user must have moodle/category:viewhiddencategories
  78       */
  79      public function test_action_category_hide_and_show() {
  80          global $DB;
  81          $this->resetAfterTest(true);
  82  
  83          $generator = $this->getDataGenerator();
  84          $category = $generator->create_category();
  85          $subcategory = $generator->create_category(array('parent' => $category->id));
  86          $course = $generator->create_course(array('category' => $subcategory->id));
  87          $context = $category->get_context();
  88          $subcontext = $subcategory->get_context();
  89          $parentcontext = $context->get_parent_context();
  90          list($user, $roleid) = $this->get_user_objects($generator, $parentcontext->id);
  91  
  92          $this->assertEquals(1, $category->visible);
  93  
  94          $parentassignment = course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $parentcontext->id);
  95          course_capability_assignment::allow(self::CATEGORY_VIEWHIDDEN, $roleid, $parentcontext->id);
  96          course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $context->id);
  97          course_capability_assignment::allow(array(self::COURSE_VIEW, self::COURSE_VIEWHIDDEN), $roleid, $subcontext->id);
  98  
  99          $this->assertTrue(\core_course\management\helper::action_category_hide($category));
 100          $cat = core_course_category::get($category->id);
 101          $subcat = core_course_category::get($subcategory->id);
 102          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 103          $this->assertEquals(0, $cat->visible);
 104          $this->assertEquals(0, $cat->visibleold);
 105          $this->assertEquals(0, $subcat->visible);
 106          $this->assertEquals(1, $subcat->visibleold);
 107          $this->assertEquals(0, $course->visible);
 108          $this->assertEquals(1, $course->visibleold);
 109          // This doesn't change anything but should succeed still.
 110          $this->assertTrue(\core_course\management\helper::action_category_hide($category));
 111          $cat = core_course_category::get($category->id);
 112          $subcat = core_course_category::get($subcategory->id);
 113          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 114          $this->assertEquals(0, $cat->visible);
 115          $this->assertEquals(0, $cat->visibleold);
 116          $this->assertEquals(0, $subcat->visible);
 117          $this->assertEquals(1, $subcat->visibleold);
 118          $this->assertEquals(0, $course->visible);
 119          $this->assertEquals(1, $course->visibleold);
 120  
 121          $this->assertTrue(\core_course\management\helper::action_category_show($category));
 122          $cat = core_course_category::get($category->id);
 123          $subcat = core_course_category::get($subcategory->id);
 124          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 125          $this->assertEquals(1, $cat->visible);
 126          $this->assertEquals(1, $cat->visibleold);
 127          $this->assertEquals(1, $subcat->visible);
 128          $this->assertEquals(1, $subcat->visibleold);
 129          $this->assertEquals(1, $course->visible);
 130          $this->assertEquals(1, $course->visibleold);
 131          // This doesn't change anything but should succeed still.
 132          $this->assertTrue(\core_course\management\helper::action_category_show($category));
 133          $cat = core_course_category::get($category->id);
 134          $subcat = core_course_category::get($subcategory->id);
 135          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 136          $this->assertEquals(1, $cat->visible);
 137          $this->assertEquals(1, $cat->visibleold);
 138          $this->assertEquals(1, $subcat->visible);
 139          $this->assertEquals(1, $subcat->visibleold);
 140          $this->assertEquals(1, $course->visible);
 141          $this->assertEquals(1, $course->visibleold);
 142  
 143          $parentassignment->assign(CAP_PROHIBIT);
 144  
 145          try {
 146              \core_course\management\helper::action_category_hide($category);
 147              $this->fail('Expected exception did not occur when trying to hide a category without permission.');
 148          } catch (\moodle_exception $ex) {
 149              // The category must still be visible.
 150              $cat = core_course_category::get($category->id);
 151              $subcat = core_course_category::get($subcategory->id);
 152              $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 153              $this->assertEquals(1, $cat->visible);
 154              $this->assertEquals(1, $cat->visibleold);
 155              $this->assertEquals(1, $subcat->visible);
 156              $this->assertEquals(1, $subcat->visibleold);
 157              $this->assertEquals(1, $course->visible);
 158              $this->assertEquals(1, $course->visibleold);
 159          }
 160  
 161          // Hide the category so that we can test helper::show.
 162          $parentassignment->assign(CAP_ALLOW);
 163          \core_course\management\helper::action_category_hide($category);
 164          $cat = core_course_category::get($category->id);
 165          $subcat = core_course_category::get($subcategory->id);
 166          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 167          $this->assertEquals(0, $cat->visible);
 168          $this->assertEquals(0, $cat->visibleold);
 169          $this->assertEquals(0, $subcat->visible);
 170          $this->assertEquals(1, $subcat->visibleold);
 171          $this->assertEquals(0, $course->visible);
 172          $this->assertEquals(1, $course->visibleold);
 173  
 174          $parentassignment->assign(CAP_PROHIBIT);
 175  
 176          try {
 177              \core_course\management\helper::action_category_show($category);
 178              $this->fail('Expected exception did not occur when trying to show a category without permission.');
 179          } catch (\moodle_exception $ex) {
 180              // The category must still be hidden.
 181              $cat = core_course_category::get($category->id);
 182              $subcat = core_course_category::get($subcategory->id);
 183              $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 184              $this->assertEquals(0, $cat->visible);
 185              $this->assertEquals(0, $cat->visibleold);
 186              $this->assertEquals(0, $subcat->visible);
 187              $this->assertEquals(1, $subcat->visibleold);
 188              $this->assertEquals(0, $course->visible);
 189              $this->assertEquals(1, $course->visibleold);
 190          }
 191  
 192          $parentassignment->assign(CAP_PREVENT);
 193          // Now we have capability on the category and subcategory but not the parent.
 194          // Try to mark the subcategory as visible. This should be possible although its parent is set to hidden.
 195          $this->assertTrue(\core_course\management\helper::action_category_show($subcategory));
 196          $cat = core_course_category::get($category->id);
 197          $subcat = core_course_category::get($subcategory->id);
 198          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 199          $this->assertEquals(0, $cat->visible);
 200          $this->assertEquals(0, $cat->visibleold);
 201          $this->assertEquals(0, $subcat->visible);
 202          $this->assertEquals(1, $subcat->visibleold);
 203          $this->assertEquals(0, $course->visible);
 204          $this->assertEquals(1, $course->visibleold);
 205  
 206          // Now make the parent visible for the next test.
 207          $parentassignment->assign(CAP_ALLOW);
 208          $this->assertTrue(\core_course\management\helper::action_category_show($category));
 209          $cat = core_course_category::get($category->id);
 210          $subcat = core_course_category::get($subcategory->id);
 211          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 212          $this->assertEquals(1, $cat->visible);
 213          $this->assertEquals(1, $cat->visibleold);
 214          $this->assertEquals(1, $subcat->visible);
 215          $this->assertEquals(1, $subcat->visibleold);
 216          $this->assertEquals(1, $course->visible);
 217          $this->assertEquals(1, $course->visibleold);
 218  
 219          $parentassignment->assign(CAP_PREVENT);
 220          // Make sure we can change the subcategory visibility.
 221          $this->assertTrue(\core_course\management\helper::action_category_hide($subcategory));
 222          // But not the category visibility.
 223          try {
 224              \core_course\management\helper::action_category_hide($category);
 225              $this->fail('Expected exception did not occur when trying to hide a category without permission.');
 226          } catch (\moodle_exception $ex) {
 227              // The category must still be visible.
 228              $this->assertEquals(1, core_course_category::get($category->id)->visible);
 229          }
 230      }
 231  
 232      /**
 233       * Tests hiding and showing of a category by its ID.
 234       *
 235       * This mimics the logic of {@link test_action_category_hide_and_show()}
 236       */
 237      public function test_action_category_hide_and_show_by_id() {
 238          global $DB;
 239          $this->resetAfterTest(true);
 240  
 241          $generator = $this->getDataGenerator();
 242          $category = $generator->create_category();
 243          $subcategory = $generator->create_category(array('parent' => $category->id));
 244          $course = $generator->create_course(array('category' => $subcategory->id));
 245          $context = $category->get_context();
 246          $parentcontext = $context->get_parent_context();
 247          $subcontext = $subcategory->get_context();
 248          list($user, $roleid) = $this->get_user_objects($generator, $parentcontext->id);
 249  
 250          $this->assertEquals(1, $category->visible);
 251  
 252          $parentassignment = course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $parentcontext->id);
 253          course_capability_assignment::allow(self::CATEGORY_VIEWHIDDEN, $roleid, $parentcontext->id);
 254          course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $context->id);
 255          course_capability_assignment::allow(array(self::COURSE_VIEW, self::COURSE_VIEWHIDDEN), $roleid, $subcontext->id);
 256  
 257          $this->assertTrue(\core_course\management\helper::action_category_hide_by_id($category->id));
 258          $cat = core_course_category::get($category->id);
 259          $subcat = core_course_category::get($subcategory->id);
 260          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 261          $this->assertEquals(0, $cat->visible);
 262          $this->assertEquals(0, $cat->visibleold);
 263          $this->assertEquals(0, $subcat->visible);
 264          $this->assertEquals(1, $subcat->visibleold);
 265          $this->assertEquals(0, $course->visible);
 266          $this->assertEquals(1, $course->visibleold);
 267          // This doesn't change anything but should succeed still.
 268          $this->assertTrue(\core_course\management\helper::action_category_hide_by_id($category->id));
 269          $cat = core_course_category::get($category->id);
 270          $subcat = core_course_category::get($subcategory->id);
 271          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 272          $this->assertEquals(0, $cat->visible);
 273          $this->assertEquals(0, $cat->visibleold);
 274          $this->assertEquals(0, $subcat->visible);
 275          $this->assertEquals(1, $subcat->visibleold);
 276          $this->assertEquals(0, $course->visible);
 277          $this->assertEquals(1, $course->visibleold);
 278  
 279          $this->assertTrue(\core_course\management\helper::action_category_show_by_id($category->id));
 280          $cat = core_course_category::get($category->id);
 281          $subcat = core_course_category::get($subcategory->id);
 282          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 283          $this->assertEquals(1, $cat->visible);
 284          $this->assertEquals(1, $cat->visibleold);
 285          $this->assertEquals(1, $subcat->visible);
 286          $this->assertEquals(1, $subcat->visibleold);
 287          $this->assertEquals(1, $course->visible);
 288          $this->assertEquals(1, $course->visibleold);
 289          // This doesn't change anything but should succeed still.
 290          $this->assertTrue(\core_course\management\helper::action_category_show_by_id($category->id));
 291          $cat = core_course_category::get($category->id);
 292          $subcat = core_course_category::get($subcategory->id);
 293          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 294          $this->assertEquals(1, $cat->visible);
 295          $this->assertEquals(1, $cat->visibleold);
 296          $this->assertEquals(1, $subcat->visible);
 297          $this->assertEquals(1, $subcat->visibleold);
 298          $this->assertEquals(1, $course->visible);
 299          $this->assertEquals(1, $course->visibleold);
 300  
 301          $parentassignment->assign(CAP_PROHIBIT);
 302  
 303          try {
 304              \core_course\management\helper::action_category_hide_by_id($category->id);
 305              $this->fail('Expected exception did not occur when trying to hide a category without permission.');
 306          } catch (\moodle_exception $ex) {
 307              // The category must still be visible.
 308              $cat = core_course_category::get($category->id);
 309              $subcat = core_course_category::get($subcategory->id);
 310              $this->assertEquals(1, $cat->visible);
 311              $this->assertEquals(1, $cat->visibleold);
 312              $this->assertEquals(1, $subcat->visible);
 313              $this->assertEquals(1, $subcat->visibleold);
 314              $this->assertEquals(1, $course->visible);
 315              $this->assertEquals(1, $course->visibleold);
 316          }
 317  
 318          // Hide the category so that we can test helper::show.
 319          $parentassignment->assign(CAP_ALLOW);
 320          \core_course\management\helper::action_category_hide_by_id($category->id);
 321          $cat = core_course_category::get($category->id);
 322          $subcat = core_course_category::get($subcategory->id);
 323          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 324          $this->assertEquals(0, $cat->visible);
 325          $this->assertEquals(0, $cat->visibleold);
 326          $this->assertEquals(0, $subcat->visible);
 327          $this->assertEquals(1, $subcat->visibleold);
 328          $this->assertEquals(0, $course->visible);
 329          $this->assertEquals(1, $course->visibleold);
 330  
 331          $parentassignment->assign(CAP_PROHIBIT);
 332  
 333          try {
 334              \core_course\management\helper::action_category_show_by_id($category->id);
 335              $this->fail('Expected exception did not occur when trying to show a category without permission.');
 336          } catch (\moodle_exception $ex) {
 337              // The category must still be hidden.
 338              $cat = core_course_category::get($category->id);
 339              $subcat = core_course_category::get($subcategory->id);
 340              $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 341              $this->assertEquals(0, $cat->visible);
 342              $this->assertEquals(0, $cat->visibleold);
 343              $this->assertEquals(0, $subcat->visible);
 344              $this->assertEquals(1, $subcat->visibleold);
 345              $this->assertEquals(0, $course->visible);
 346              $this->assertEquals(1, $course->visibleold);
 347          }
 348  
 349          $parentassignment->assign(CAP_PREVENT);
 350          // Now we have capability on the category and subcategory but not the parent.
 351          // Try to mark the subcategory as visible. This should be possible although its parent is set to hidden.
 352          $this->assertTrue(\core_course\management\helper::action_category_show($subcategory));
 353          $cat = core_course_category::get($category->id);
 354          $subcat = core_course_category::get($subcategory->id);
 355          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 356          $this->assertEquals(0, $cat->visible);
 357          $this->assertEquals(0, $cat->visibleold);
 358          $this->assertEquals(0, $subcat->visible);
 359          $this->assertEquals(1, $subcat->visibleold);
 360          $this->assertEquals(0, $course->visible);
 361          $this->assertEquals(1, $course->visibleold);
 362  
 363          // Now make the parent visible for the next test.
 364          $parentassignment->assign(CAP_ALLOW);
 365          $this->assertTrue(\core_course\management\helper::action_category_show_by_id($category->id));
 366          $cat = core_course_category::get($category->id);
 367          $subcat = core_course_category::get($subcategory->id);
 368          $course = $DB->get_record('course', array('id' => $course->id), 'id, visible, visibleold', MUST_EXIST);
 369          $this->assertEquals(1, $cat->visible);
 370          $this->assertEquals(1, $cat->visibleold);
 371          $this->assertEquals(1, $subcat->visible);
 372          $this->assertEquals(1, $subcat->visibleold);
 373          $this->assertEquals(1, $course->visible);
 374          $this->assertEquals(1, $course->visibleold);
 375  
 376          $parentassignment->assign(CAP_PREVENT);
 377          // Make sure we can change the subcategory visibility.
 378          $this->assertTrue(\core_course\management\helper::action_category_hide($subcategory));
 379          // But not the category visibility.
 380          try {
 381              \core_course\management\helper::action_category_hide_by_id($category->id);
 382              $this->fail('Expected exception did not occur when trying to hide a category without permission.');
 383          } catch (\moodle_exception $ex) {
 384              // The category must still be visible.
 385              $this->assertEquals(1, core_course_category::get($category->id)->visible);
 386          }
 387      }
 388  
 389      /**
 390       * Test moving courses between categories.
 391       */
 392      public function test_action_category_move_courses_into() {
 393          global $DB, $CFG;
 394          $this->resetAfterTest(true);
 395  
 396          $generator = $this->getDataGenerator();
 397          $cat1 = $generator->create_category();
 398          $cat2 = $generator->create_category();
 399          $sub1 = $generator->create_category(array('parent' => $cat1->id));
 400          $sub2 = $generator->create_category(array('parent' => $cat1->id));
 401          $course1 = $generator->create_course(array('category' => $cat1->id));
 402          $course2 = $generator->create_course(array('category' => $sub1->id));
 403          $course3 = $generator->create_course(array('category' => $sub1->id));
 404          $course4 = $generator->create_course(array('category' => $cat2->id));
 405  
 406          $syscontext = \context_system::instance();
 407  
 408          list($user, $roleid) = $this->get_user_objects($generator, $syscontext->id);
 409  
 410          course_capability_assignment::allow(array(self::CATEGORY_MANAGE, self::CATEGORY_VIEWHIDDEN), $roleid, $syscontext->id);
 411  
 412          // Check they are where we think they are.
 413          $this->assertEquals(1, $cat1->get_courses_count());
 414          $this->assertEquals(1, $cat2->get_courses_count());
 415          $this->assertEquals(2, $sub1->get_courses_count());
 416          $this->assertEquals(0, $sub2->get_courses_count());
 417  
 418          // Move the courses in sub category 1 to sub category 2.
 419          $this->assertTrue(
 420              \core_course\management\helper::action_category_move_courses_into($sub1, $sub2, array($course2->id, $course3->id))
 421          );
 422  
 423          $this->assertEquals(1, $cat1->get_courses_count());
 424          $this->assertEquals(1, $cat2->get_courses_count());
 425          $this->assertEquals(0, $sub1->get_courses_count());
 426          $this->assertEquals(2, $sub2->get_courses_count());
 427  
 428          $courses = $DB->get_records('course', array('category' => $sub2->id), 'id');
 429          $this->assertEquals(array((int)$course2->id, (int)$course3->id), array_keys($courses));
 430  
 431          // Move the courses in sub category 2 back into to sub category 1.
 432          $this->assertTrue(
 433              \core_course\management\helper::action_category_move_courses_into($sub2, $sub1, array($course2->id, $course3->id))
 434          );
 435  
 436          $this->assertEquals(1, $cat1->get_courses_count());
 437          $this->assertEquals(1, $cat2->get_courses_count());
 438          $this->assertEquals(2, $sub1->get_courses_count());
 439          $this->assertEquals(0, $sub2->get_courses_count());
 440  
 441          $courses = $DB->get_records('course', array('category' => $sub1->id), 'id');
 442          $this->assertEquals(array((int)$course2->id, (int)$course3->id), array_keys($courses));
 443  
 444          // Try moving just one course.
 445          $this->assertTrue(
 446              \core_course\management\helper::action_category_move_courses_into($cat2, $sub2, array($course4->id))
 447          );
 448          $this->assertEquals(1, $cat1->get_courses_count());
 449          $this->assertEquals(0, $cat2->get_courses_count());
 450          $this->assertEquals(2, $sub1->get_courses_count());
 451          $this->assertEquals(1, $sub2->get_courses_count());
 452          $courses = $DB->get_records('course', array('category' => $sub2->id), 'id');
 453          $this->assertEquals(array((int)$course4->id), array_keys($courses));
 454  
 455          // Try moving a course from a category its not part of.
 456          try {
 457              \core_course\management\helper::action_category_move_courses_into($cat2, $sub2, array($course4->id));
 458              $this->fail('Moved a course from a category it wasn\'t within');
 459          } catch (\moodle_exception $exception) {
 460              // Check that everything is as it was.
 461              $this->assertEquals(1, $cat1->get_courses_count());
 462              $this->assertEquals(0, $cat2->get_courses_count());
 463              $this->assertEquals(2, $sub1->get_courses_count());
 464              $this->assertEquals(1, $sub2->get_courses_count());
 465          }
 466  
 467          // Now try that again with two courses, one of which is in the right place.
 468          try {
 469              \core_course\management\helper::action_category_move_courses_into($cat2, $sub2, array($course4->id, $course1->id));
 470              $this->fail('Moved a course from a category it wasn\'t within');
 471          } catch (\moodle_exception $exception) {
 472              // Check that everything is as it was. Nothing should have been moved.
 473              $this->assertEquals(1, $cat1->get_courses_count());
 474              $this->assertEquals(0, $cat2->get_courses_count());
 475              $this->assertEquals(2, $sub1->get_courses_count());
 476              $this->assertEquals(1, $sub2->get_courses_count());
 477          }
 478  
 479          // Current state:
 480          // * $cat1 => $course1
 481          //    * $sub1 => $course2, $course3
 482          //    * $sub2 => $course4
 483          // * $cat2 =>.
 484  
 485          // Prevent the user from being able to move into $sub2.
 486          $sub2cap = course_capability_assignment::prohibit(self::CATEGORY_MANAGE, $roleid, $sub2->get_context()->id);
 487          $sub2 = core_course_category::get($sub2->id);
 488          // Suppress debugging messages for a moment.
 489          $olddebug = $CFG->debug;
 490          $CFG->debug = 0;
 491  
 492          // Try to move a course into sub2. This shouldn't be possible because you should always be able to undo what you've done.
 493          // Try moving just one course.
 494          try {
 495              \core_course\management\helper::action_category_move_courses_into($sub1, $sub2, array($course2->id));
 496              $this->fail('Invalid move of course between categories, action can\'t be undone.');
 497          } catch (\moodle_exception $ex) {
 498              $this->assertEquals(get_string('cannotmovecourses', 'error'), $ex->getMessage());
 499          }
 500          // Nothing should have changed.
 501          $this->assertEquals(1, $cat1->get_courses_count());
 502          $this->assertEquals(0, $cat2->get_courses_count());
 503          $this->assertEquals(2, $sub1->get_courses_count());
 504          $this->assertEquals(1, $sub2->get_courses_count());
 505  
 506          // Now try moving a course out of sub2. Again should not be possible.
 507          // Try to move a course into sub2. This shouldn't be possible because you should always be able to undo what you've done.
 508          // Try moving just one course.
 509          try {
 510              \core_course\management\helper::action_category_move_courses_into($sub2, $cat2, array($course4->id));
 511              $this->fail('Invalid move of course between categories, action can\'t be undone.');
 512          } catch (\moodle_exception $ex) {
 513              $this->assertEquals(get_string('cannotmovecourses', 'error'), $ex->getMessage());
 514          }
 515          // Nothing should have changed.
 516          $this->assertEquals(1, $cat1->get_courses_count());
 517          $this->assertEquals(0, $cat2->get_courses_count());
 518          $this->assertEquals(2, $sub1->get_courses_count());
 519          $this->assertEquals(1, $sub2->get_courses_count());
 520  
 521          $CFG->debug = $olddebug;
 522      }
 523  
 524      /**
 525       * Test moving a categories up and down.
 526       */
 527      public function test_action_category_movedown_and_moveup() {
 528          global $DB;
 529          $this->resetAfterTest(true);
 530  
 531          $generator = $this->getDataGenerator();
 532          $parent = $generator->create_category();
 533          $cat1 = $generator->create_category(array('parent' => $parent->id, 'name' => 'One'));
 534          $cat2 = $generator->create_category(array('parent' => $parent->id, 'name' => 'Two'));
 535          $cat3 = $generator->create_category(array('parent' => $parent->id, 'name' => 'Three'));
 536  
 537          $syscontext = \context_system::instance();
 538          list($user, $roleid) = $this->get_user_objects($generator, $syscontext->id);
 539          course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $syscontext->id);
 540  
 541          // Check everything is where we expect it to be.
 542          $this->assertEquals(
 543              array('One', 'Two', 'Three'),
 544              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 545          );
 546  
 547          // Move the top category down one.
 548          $this->assertTrue(\core_course\management\helper::action_category_change_sortorder_down_one($cat1));
 549          // Reload out objects.
 550          $cat1 = core_course_category::get($cat1->id);
 551          $cat2 = core_course_category::get($cat2->id);
 552          $cat3 = core_course_category::get($cat3->id);
 553          // Verify that caches were cleared.
 554          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat1->id)), $cat1->sortorder);
 555          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat2->id)), $cat2->sortorder);
 556          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat3->id)), $cat3->sortorder);
 557          // Verify sorting.
 558          $this->assertEquals(
 559              array('Two', 'One', 'Three'),
 560              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 561          );
 562  
 563          // Move the bottom category up one.
 564          $this->assertTrue(\core_course\management\helper::action_category_change_sortorder_up_one($cat3));
 565          // Reload out objects.
 566          $cat1 = core_course_category::get($cat1->id);
 567          $cat2 = core_course_category::get($cat2->id);
 568          $cat3 = core_course_category::get($cat3->id);
 569          // Verify that caches were cleared.
 570          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat1->id)), $cat1->sortorder);
 571          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat2->id)), $cat2->sortorder);
 572          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat3->id)), $cat3->sortorder);
 573          // Verify sorting.
 574          $this->assertEquals(
 575              array('Two', 'Three', 'One'),
 576              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 577          );
 578  
 579          // Move the top category down one.
 580          $this->assertTrue(\core_course\management\helper::action_category_change_sortorder_down_one_by_id($cat2->id));
 581          $this->assertEquals(
 582              array('Three', 'Two', 'One'),
 583              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 584          );
 585  
 586          // Move the top category down one.
 587          $this->assertTrue(\core_course\management\helper::action_category_change_sortorder_up_one_by_id($cat1->id));
 588          $this->assertEquals(
 589              array('Three', 'One', 'Two'),
 590              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 591          );
 592  
 593          // Reload out objects the above actions will have caused the objects to become stale.
 594          $cat1 = core_course_category::get($cat1->id);
 595          $cat2 = core_course_category::get($cat2->id);
 596          $cat3 = core_course_category::get($cat3->id);
 597          // Verify that caches were cleared.
 598          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat1->id)), $cat1->sortorder);
 599          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat2->id)), $cat2->sortorder);
 600          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat3->id)), $cat3->sortorder);
 601          // Verify sorting.
 602  
 603          // Test moving the top category up one. Nothing should change but it should return false.
 604          $this->assertFalse(\core_course\management\helper::action_category_change_sortorder_up_one($cat3));
 605          // Reload out objects.
 606          $cat1 = core_course_category::get($cat1->id);
 607          $cat2 = core_course_category::get($cat2->id);
 608          $cat3 = core_course_category::get($cat3->id);
 609          // Verify that caches were cleared.
 610          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat1->id)), $cat1->sortorder);
 611          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat2->id)), $cat2->sortorder);
 612          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat3->id)), $cat3->sortorder);
 613          // Verify sorting.
 614          $this->assertEquals(
 615              array('Three', 'One', 'Two'),
 616              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 617          );
 618  
 619          // Test moving the bottom category down one. Nothing should change but it should return false.
 620          $this->assertFalse(\core_course\management\helper::action_category_change_sortorder_down_one($cat2));
 621          // Reload out objects.
 622          $cat1 = core_course_category::get($cat1->id);
 623          $cat2 = core_course_category::get($cat2->id);
 624          $cat3 = core_course_category::get($cat3->id);
 625          // Verify that caches were cleared.
 626          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat1->id)), $cat1->sortorder);
 627          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat2->id)), $cat2->sortorder);
 628          $this->assertEquals($DB->get_field('course_categories', 'sortorder', array('id' => $cat3->id)), $cat3->sortorder);
 629          // Verify sorting.
 630          $this->assertEquals(
 631              array('Three', 'One', 'Two'),
 632              array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 633          );
 634  
 635          // Prevent moving on the parent.
 636          course_capability_assignment::prevent(self::CATEGORY_MANAGE, $roleid, $parent->get_context()->id);
 637          try {
 638              \core_course\management\helper::action_category_change_sortorder_up_one($cat1);
 639          } catch (\moodle_exception $exception) {
 640              // Check everything is still where it should be.
 641              $this->assertEquals(
 642                  array('Three', 'One', 'Two'),
 643                  array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 644              );
 645          }
 646          try {
 647              \core_course\management\helper::action_category_change_sortorder_down_one($cat3);
 648          } catch (\moodle_exception $exception) {
 649              // Check everything is still where it should be.
 650              $this->assertEquals(
 651                  array('Three', 'One', 'Two'),
 652                  array_keys($DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder', 'name'))
 653              );
 654          }
 655      }
 656  
 657      /**
 658       * Test resorting of courses within a category.
 659       *
 660       * \core_course\management\helper::action_category_resort_courses
 661       */
 662      public function test_action_category_resort_courses() {
 663          global $DB;
 664          $this->resetAfterTest(true);
 665  
 666          $generator = $this->getDataGenerator();
 667          $category = $generator->create_category();
 668          $course1 = $generator->create_course(array('category' => $category->id, 'fullname' => 'Experimental Chemistry',
 669              'shortname' => 'Course A', 'idnumber' => '10001'));
 670          $course2 = $generator->create_course(array('category' => $category->id, 'fullname' => 'Learn to program: Jade',
 671              'shortname' => 'Beginning Jade', 'idnumber' => '10003'));
 672          $course3 = $generator->create_course(array('category' => $category->id, 'fullname' => 'Advanced algebra',
 673              'shortname' => 'Advanced algebra', 'idnumber' => '10002'));
 674          $syscontext = \context_system::instance();
 675  
 676          // Update category object from DB so the course count is correct.
 677          $category = core_course_category::get($category->id);
 678  
 679          list($user, $roleid) = $this->get_user_objects($generator, $syscontext->id);
 680          $caps = course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $syscontext->id);
 681  
 682          // Check that sort order in the DB matches what we've got in the cache.
 683          $courses = $category->get_courses();
 684          $this->assertIsArray($courses);
 685          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 686          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 687  
 688          // Resort by fullname.
 689          \core_course\management\helper::action_category_resort_courses($category, 'fullname');
 690          $courses = $category->get_courses();
 691          $this->assertIsArray($courses);
 692          $this->assertEquals(array($course3->id, $course1->id, $course2->id), array_keys($courses));
 693          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 694          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 695  
 696          // Resort by shortname.
 697          \core_course\management\helper::action_category_resort_courses($category, 'shortname');
 698          $courses = $category->get_courses();
 699          $this->assertIsArray($courses);
 700          $this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses));
 701          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 702          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 703  
 704          // Resort by idnumber.
 705          \core_course\management\helper::action_category_resort_courses($category, 'idnumber');
 706          $courses = $category->get_courses();
 707          $this->assertIsArray($courses);
 708          $this->assertEquals(array($course1->id, $course3->id, $course2->id), array_keys($courses));
 709          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 710          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 711  
 712          // Try with a field that cannot be sorted on.
 713          try {
 714              \core_course\management\helper::action_category_resort_courses($category, 'category');
 715              $this->fail('Category courses resorted by invalid sort field.');
 716          } catch (\coding_exception $exception) {
 717              // Test things are as they were before.
 718              $courses = $category->get_courses();
 719              $this->assertIsArray($courses);
 720              $this->assertEquals(array($course1->id, $course3->id, $course2->id), array_keys($courses));
 721              $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 722              $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 723          }
 724  
 725          // Try with a completely bogus field.
 726          try {
 727              \core_course\management\helper::action_category_resort_courses($category, 'monkeys');
 728              $this->fail('Category courses resorted by completely ridiculous field.');
 729          } catch (\coding_exception $exception) {
 730              // Test things are as they were before.
 731              $courses = $category->get_courses();
 732              $this->assertIsArray($courses);
 733              $this->assertEquals(array($course1->id, $course3->id, $course2->id), array_keys($courses));
 734              $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 735              $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 736          }
 737  
 738          // Prohibit resorting.
 739          $caps->assign(CAP_PROHIBIT);
 740          // Refresh our coursecat object.
 741          $category = core_course_category::get($category->id);
 742  
 743          // We should no longer have permission to do this. Test it out!
 744          try {
 745              \core_course\management\helper::action_category_resort_courses($category, 'shortname');
 746              $this->fail('Courses sorted without having the required permission.');
 747          } catch (\moodle_exception $exception) {
 748              // Check its the right exception.
 749              $this->assertEquals('core_course_category::can_resort', $exception->debuginfo);
 750              // Test things are as they were before.
 751              $courses = $category->get_courses();
 752              $this->assertIsArray($courses);
 753              $this->assertEquals(array($course1->id, $course3->id, $course2->id), array_keys($courses));
 754              $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder');
 755              $this->assertEquals(array_keys($dbcourses), array_keys($courses));
 756          }
 757      }
 758  
 759      /**
 760       * Tests resorting sub categories of a course.
 761       *
 762       * \core_course\management\helper::action_category_resort_courses
 763       */
 764      public function test_action_category_resort_subcategories() {
 765          global $DB;
 766          $this->resetAfterTest(true);
 767  
 768          $generator = $this->getDataGenerator();
 769          $parent = $generator->create_category();
 770          $cat1 = $generator->create_category(array('parent' => $parent->id, 'name' => 'School of Science', 'idnumber' => '10001'));
 771          $cat2 = $generator->create_category(array('parent' => $parent->id, 'name' => 'School of Commerce', 'idnumber' => '10003'));
 772          $cat3 = $generator->create_category(array('parent' => $parent->id, 'name' => 'School of Arts', 'idnumber' => '10002'));
 773  
 774          $syscontext = \context_system::instance();
 775          list($user, $roleid) = $this->get_user_objects($generator, $syscontext->id);
 776          $caps = course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $syscontext->id);
 777  
 778          $categories = $parent->get_children();
 779          $this->assertIsArray($categories);
 780          $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 781          $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 782  
 783          // Test sorting by name.
 784          \core_course\management\helper::action_category_resort_subcategories($parent, 'name');
 785          $categories = $parent->get_children();
 786          $this->assertIsArray($categories);
 787          $this->assertEquals(array($cat3->id, $cat2->id, $cat1->id), array_keys($categories));
 788          $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 789          $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 790  
 791          // Test sorting by idnumber.
 792          \core_course\management\helper::action_category_resort_subcategories($parent, 'idnumber');
 793          $categories = $parent->get_children();
 794          $this->assertIsArray($categories);
 795          $this->assertEquals(array($cat1->id, $cat3->id, $cat2->id), array_keys($categories));
 796          $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 797          $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 798  
 799          // Try with an invalid field.
 800          try {
 801              \core_course\management\helper::action_category_resort_subcategories($parent, 'summary');
 802              $this->fail('Categories resorted by invalid field.');
 803          } catch (\coding_exception $exception) {
 804              // Check that nothing was changed.
 805              $categories = $parent->get_children();
 806              $this->assertIsArray($categories);
 807              $this->assertEquals(array($cat1->id, $cat3->id, $cat2->id), array_keys($categories));
 808              $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 809              $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 810          }
 811  
 812          // Try with a completely bogus field.
 813          try {
 814              \core_course\management\helper::action_category_resort_subcategories($parent, 'monkeys');
 815              $this->fail('Categories resorted by completely bogus field.');
 816          } catch (\coding_exception $exception) {
 817              // Check that nothing was changed.
 818              $categories = $parent->get_children();
 819              $this->assertIsArray($categories);
 820              $this->assertEquals(array($cat1->id, $cat3->id, $cat2->id), array_keys($categories));
 821              $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 822              $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 823          }
 824  
 825          // Test resorting the top level category (puke).
 826          $topcat = core_course_category::get(0);
 827          \core_course\management\helper::action_category_resort_subcategories($topcat, 'name');
 828          $categories = $topcat->get_children();
 829          $this->assertIsArray($categories);
 830          $dbcategories = $DB->get_records('course_categories', array('parent' => '0'), 'sortorder');
 831          $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 832  
 833          // Prohibit resorting.
 834          $caps->assign(CAP_PROHIBIT);
 835          // Refresh our coursecat object.
 836          $parent = core_course_category::get($parent->id);
 837  
 838          // We should no longer have permission to do this. Test it out!
 839          try {
 840              \core_course\management\helper::action_category_resort_subcategories($parent, 'idnumber');
 841              $this->fail('Categories sorted without having the required permission.');
 842          } catch (\moodle_exception $exception) {
 843              // Check its the right exception.
 844              $this->assertEquals('core_course_category::can_resort', $exception->debuginfo);
 845              // Test things are as they were before.
 846              $categories = $parent->get_children();
 847              $this->assertIsArray($categories);
 848              $this->assertEquals(array($cat1->id, $cat3->id, $cat2->id), array_keys($categories));
 849              $dbcategories = $DB->get_records('course_categories', array('parent' => $parent->id), 'sortorder');
 850              $this->assertEquals(array_keys($dbcategories), array_keys($categories));
 851          }
 852      }
 853  
 854      /**
 855       * Test hiding and showing of a course.
 856       *
 857       * @see \core_course\management\helper::action_course_hide
 858       * @see \core_course\management\helper::action_course_show
 859       */
 860      public function test_action_course_hide_show() {
 861          $this->resetAfterTest(true);
 862  
 863          $generator = $this->getDataGenerator();
 864          $category = $generator->create_category();
 865          $course = $generator->create_course();
 866  
 867          $coursecontext = \context_course::instance($course->id);
 868  
 869          list($user, $roleid) = $this->get_user_objects($generator, $coursecontext->id);
 870          $caps = array(self::COURSE_VIEW, self::COURSE_VIEWHIDDEN);
 871          $assignment = course_capability_assignment::allow($caps, $roleid, $coursecontext->id);
 872  
 873          $course = new core_course_list_element(get_course($course->id));
 874  
 875          // Check it is set to what we think it is.
 876          $this->assertEquals('1', $course->visible);
 877          $this->assertEquals('1', $course->visibleold);
 878  
 879          // Test hiding the course.
 880          $this->assertTrue(\core_course\management\helper::action_course_hide($course));
 881          // Refresh the course.
 882          $course = new core_course_list_element(get_course($course->id));
 883          $this->assertEquals('0', $course->visible);
 884          $this->assertEquals('0', $course->visibleold);
 885  
 886          // Test hiding the course again.
 887          $this->assertTrue(\core_course\management\helper::action_course_hide($course));
 888          // Refresh the course.
 889          $course = new core_course_list_element(get_course($course->id));
 890          $this->assertEquals('0', $course->visible);
 891          $this->assertEquals('0', $course->visibleold);
 892  
 893          // Test showing the course.
 894          $this->assertTrue(\core_course\management\helper::action_course_show($course));
 895          // Refresh the course.
 896          $course = new core_course_list_element(get_course($course->id));
 897          $this->assertEquals('1', $course->visible);
 898          $this->assertEquals('1', $course->visibleold);
 899  
 900          // Test showing the course again. Shouldn't change anything.
 901          $this->assertTrue(\core_course\management\helper::action_course_show($course));
 902          // Refresh the course.
 903          $course = new core_course_list_element(get_course($course->id));
 904          $this->assertEquals('1', $course->visible);
 905          $this->assertEquals('1', $course->visibleold);
 906  
 907          // Revoke the permissions.
 908          $assignment->revoke();
 909          $course = new core_course_list_element(get_course($course->id));
 910  
 911          try {
 912              \core_course\management\helper::action_course_show($course);
 913          } catch (\moodle_exception $exception) {
 914              $this->assertEquals('core_course_list_element::can_change_visbility', $exception->debuginfo);
 915          }
 916      }
 917  
 918      /**
 919       * Test hiding and showing of a course.
 920       *
 921       * @see \core_course\management\helper::action_course_hide_by_record
 922       * @see \core_course\management\helper::action_course_show_by_record
 923       */
 924      public function test_action_course_hide_show_by_record() {
 925          $this->resetAfterTest(true);
 926  
 927          $generator = $this->getDataGenerator();
 928          $category = $generator->create_category();
 929          $course = $generator->create_course();
 930  
 931          $coursecontext = \context_course::instance($course->id);
 932  
 933          list($user, $roleid) = $this->get_user_objects($generator, $coursecontext->id);
 934          $caps = array(self::COURSE_VIEW, self::COURSE_VIEWHIDDEN);
 935          $assignment = course_capability_assignment::allow($caps, $roleid, $coursecontext->id);
 936  
 937          $course = get_course($course->id);
 938  
 939          // Check it is set to what we think it is.
 940          $this->assertEquals('1', $course->visible);
 941          $this->assertEquals('1', $course->visibleold);
 942  
 943          // Test hiding the course.
 944          $this->assertTrue(\core_course\management\helper::action_course_hide_by_record($course));
 945          // Refresh the course.
 946          $course = get_course($course->id);
 947          $this->assertEquals('0', $course->visible);
 948          $this->assertEquals('0', $course->visibleold);
 949  
 950          // Test hiding the course again. Shouldn't change anything.
 951          $this->assertTrue(\core_course\management\helper::action_course_hide_by_record($course));
 952          // Refresh the course.
 953          $course = get_course($course->id);
 954          $this->assertEquals('0', $course->visible);
 955          $this->assertEquals('0', $course->visibleold);
 956  
 957          // Test showing the course.
 958          $this->assertTrue(\core_course\management\helper::action_course_show_by_record($course));
 959          // Refresh the course.
 960          $course = get_course($course->id);
 961          $this->assertEquals('1', $course->visible);
 962          $this->assertEquals('1', $course->visibleold);
 963  
 964          // Test showing the course again. Shouldn't change anything.
 965          $this->assertTrue(\core_course\management\helper::action_course_show_by_record($course));
 966          // Refresh the course.
 967          $course = get_course($course->id);
 968          $this->assertEquals('1', $course->visible);
 969          $this->assertEquals('1', $course->visibleold);
 970  
 971          // Revoke the permissions.
 972          $assignment->revoke();
 973          $course = get_course($course->id);
 974  
 975          try {
 976              \core_course\management\helper::action_course_show_by_record($course);
 977          } catch (\moodle_exception $exception) {
 978              $this->assertEquals('core_course_list_element::can_change_visbility', $exception->debuginfo);
 979          }
 980      }
 981  
 982      /**
 983       * Tests moving a course up and down by one.
 984       */
 985      public function test_action_course_movedown_and_moveup() {
 986          global $DB;
 987  
 988          $this->resetAfterTest(true);
 989  
 990          $generator = $this->getDataGenerator();
 991          $category = $generator->create_category();
 992          $course3 = $generator->create_course(array('category' => $category->id));
 993          $course2 = $generator->create_course(array('category' => $category->id));
 994          $course1 = $generator->create_course(array('category' => $category->id));
 995          $context = $category->get_context();
 996  
 997          // Update category object from DB so the course count is correct.
 998          $category = core_course_category::get($category->id);
 999  
1000          list($user, $roleid) = $this->get_user_objects($generator, $context->id);
1001          $caps = course_capability_assignment::allow(self::CATEGORY_MANAGE, $roleid, $context->id);
1002  
1003          $courses = $category->get_courses();
1004          $this->assertIsArray($courses);
1005          $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
1006          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1007          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1008  
1009          // Move a course down.
1010          $this->assertTrue(
1011              \core_course\management\helper::action_course_change_sortorder_down_one(
1012                  new core_course_list_element(get_course($course1->id)), $category)
1013          );
1014          $courses = $category->get_courses();
1015          $this->assertIsArray($courses);
1016          $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
1017          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1018          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1019  
1020          // Move a course up.
1021          $this->assertTrue(
1022              \core_course\management\helper::action_course_change_sortorder_up_one(
1023                  new core_course_list_element(get_course($course3->id)), $category)
1024          );
1025          $courses = $category->get_courses();
1026          $this->assertIsArray($courses);
1027          $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
1028          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1029          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1030  
1031          // Move a course down by record.
1032          $this->assertTrue(
1033              \core_course\management\helper::action_course_change_sortorder_down_one_by_record(get_course($course2->id), $category)
1034          );
1035          $courses = $category->get_courses();
1036          $this->assertIsArray($courses);
1037          $this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses));
1038          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1039          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1040  
1041          // Move a course up by record.
1042          $this->assertTrue(
1043              \core_course\management\helper::action_course_change_sortorder_up_one_by_record(get_course($course2->id), $category)
1044          );
1045          $courses = $category->get_courses();
1046          $this->assertIsArray($courses);
1047          $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
1048          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1049          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1050  
1051          // Try move the bottom course down. This should return false and nothing changes.
1052          $this->assertFalse(
1053              \core_course\management\helper::action_course_change_sortorder_down_one(
1054                  new core_course_list_element(get_course($course1->id)), $category)
1055          );
1056          $courses = $category->get_courses();
1057          $this->assertIsArray($courses);
1058          $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
1059          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1060          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1061  
1062          // Try move the top course up. This should return false and nothing changes.
1063          $this->assertFalse(
1064              \core_course\management\helper::action_course_change_sortorder_up_one(
1065                  new core_course_list_element(get_course($course2->id)), $category)
1066          );
1067          $courses = $category->get_courses();
1068          $this->assertIsArray($courses);
1069          $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
1070          $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1071          $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1072  
1073          // Prohibit the ability to move.
1074          $caps->assign(CAP_PROHIBIT);
1075          // Reload the category.
1076          $category = core_course_category::get($category->id);
1077  
1078          try {
1079              \core_course\management\helper::action_course_change_sortorder_down_one(
1080                  new core_course_list_element(get_course($course2->id)), $category);
1081              $this->fail('Course moved without having the required permissions.');
1082          } catch (\moodle_exception $exception) {
1083              // Check nothing has changed.
1084              $courses = $category->get_courses();
1085              $this->assertIsArray($courses);
1086              $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
1087              $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
1088              $this->assertEquals(array_keys($dbcourses), array_keys($courses));
1089          }
1090      }
1091  
1092      /**
1093       * Tests the fetching of actions for a category.
1094       */
1095      public function test_get_category_listitem_actions() {
1096          global $PAGE;
1097          $this->resetAfterTest(true);
1098  
1099          $PAGE->set_url(new \moodle_url('/course/management.php'));
1100  
1101          $generator = $this->getDataGenerator();
1102          $category = $generator->create_category();
1103          $context = \context_system::instance();
1104          list($user, $roleid) = $this->get_user_objects($generator, $context->id);
1105          course_capability_assignment::allow(array(
1106              self::CATEGORY_MANAGE,
1107              self::CATEGORY_VIEWHIDDEN,
1108              'moodle/role:assign',
1109              'moodle/cohort:view',
1110              'moodle/filter:manage'
1111          ), $roleid, $context->id);
1112  
1113          $actions = \core_course\management\helper::get_category_listitem_actions($category);
1114          $this->assertIsArray($actions);
1115          $this->assertArrayHasKey('edit', $actions);
1116          $this->assertArrayHasKey('hide', $actions);
1117          $this->assertArrayHasKey('show', $actions);
1118          $this->assertArrayHasKey('moveup', $actions);
1119          $this->assertArrayHasKey('movedown', $actions);
1120          $this->assertArrayHasKey('delete', $actions);
1121          $this->assertArrayHasKey('assignroles', $actions);
1122          $this->assertArrayHasKey('permissions', $actions);
1123          $this->assertArrayHasKey('cohorts', $actions);
1124          $this->assertArrayHasKey('filters', $actions);
1125      }
1126  
1127      /**
1128       * Tests fetching the course actions.
1129       */
1130      public function test_get_course_detail_actions() {
1131          $this->resetAfterTest(true);
1132  
1133          $generator = $this->getDataGenerator();
1134          $category = $generator->create_category();
1135          $course = $generator->create_course();
1136          $context = \context_system::instance();
1137          list($user, $roleid) = $this->get_user_objects($generator, $context->id);
1138          $generator->enrol_user($user->id, $course->id, $roleid);
1139          course_capability_assignment::allow(array(
1140              self::COURSE_VIEW,
1141              self::COURSE_VIEWHIDDEN,
1142              'moodle/course:update',
1143              'moodle/course:enrolreview',
1144              'moodle/course:delete',
1145              'moodle/backup:backupcourse',
1146              'moodle/restore:restorecourse'
1147          ), $roleid, $context->id);
1148  
1149          $actions = \core_course\management\helper::get_course_detail_actions(new core_course_list_element($course));
1150          $this->assertIsArray($actions);
1151          $this->assertArrayHasKey('view', $actions);
1152          $this->assertArrayHasKey('edit', $actions);
1153          $this->assertArrayHasKey('enrolledusers', $actions);
1154          $this->assertArrayHasKey('delete', $actions);
1155          $this->assertArrayHasKey('hide', $actions);
1156          $this->assertArrayHasKey('backup', $actions);
1157          $this->assertArrayHasKey('restore', $actions);
1158      }
1159  
1160      /**
1161       * Test fetching course details.
1162       */
1163      public function test_get_course_detail_array() {
1164          $this->resetAfterTest(true);
1165  
1166          $generator = $this->getDataGenerator();
1167          $category = $generator->create_category();
1168          $course = $generator->create_course();
1169          $context = \context_system::instance();
1170          list($user, $roleid) = $this->get_user_objects($generator, $context->id);
1171          $generator->enrol_user($user->id, $course->id, $roleid);
1172          course_capability_assignment::allow(array(
1173              self::COURSE_VIEW,
1174              self::COURSE_VIEWHIDDEN,
1175              'moodle/course:update',
1176              'moodle/course:enrolreview',
1177              'moodle/course:delete',
1178              'moodle/backup:backupcourse',
1179              'moodle/restore:restorecourse',
1180              'moodle/site:accessallgroups'
1181          ), $roleid, $context->id);
1182  
1183          $details = \core_course\management\helper::get_course_detail_array(new core_course_list_element($course));
1184          $this->assertIsArray($details);
1185          $this->assertArrayHasKey('format', $details);
1186          $this->assertArrayHasKey('fullname', $details);
1187          $this->assertArrayHasKey('shortname', $details);
1188          $this->assertArrayHasKey('idnumber', $details);
1189          $this->assertArrayHasKey('category', $details);
1190          $this->assertArrayHasKey('groupings', $details);
1191          $this->assertArrayHasKey('groups', $details);
1192          $this->assertArrayHasKey('roleassignments', $details);
1193          $this->assertArrayHasKey('enrolmentmethods', $details);
1194          $this->assertArrayHasKey('sections', $details);
1195          $this->assertArrayHasKey('modulesused', $details);
1196      }
1197  
1198      public function test_move_courses_into_category() {
1199          global $DB, $CFG;
1200          $this->resetAfterTest(true);
1201  
1202          $generator = $this->getDataGenerator();
1203          $cat1 = $generator->create_category();
1204          $cat2 = $generator->create_category();
1205          $sub1 = $generator->create_category(array('parent' => $cat1->id));
1206          $sub2 = $generator->create_category(array('parent' => $cat1->id));
1207          $course1 = $generator->create_course(array('category' => $cat1->id));
1208          $course2 = $generator->create_course(array('category' => $sub1->id));
1209          $course3 = $generator->create_course(array('category' => $sub1->id));
1210          $course4 = $generator->create_course(array('category' => $cat2->id));
1211  
1212          $syscontext = \context_system::instance();
1213  
1214          list($user, $roleid) = $this->get_user_objects($generator, $syscontext->id);
1215  
1216          course_capability_assignment::allow(array(self::CATEGORY_MANAGE, self::CATEGORY_VIEWHIDDEN), $roleid, $syscontext->id);
1217  
1218          // Check they are where we think they are.
1219          $this->assertEquals(1, $cat1->get_courses_count());
1220          $this->assertEquals(1, $cat2->get_courses_count());
1221          $this->assertEquals(2, $sub1->get_courses_count());
1222          $this->assertEquals(0, $sub2->get_courses_count());
1223  
1224          // Move the courses in sub category 1 to sub category 2.
1225          $this->assertTrue(
1226              \core_course\management\helper::move_courses_into_category($sub2->id, array($course2->id, $course3->id))
1227          );
1228  
1229          $this->assertEquals(1, $cat1->get_courses_count());
1230          $this->assertEquals(1, $cat2->get_courses_count());
1231          $this->assertEquals(0, $sub1->get_courses_count());
1232          $this->assertEquals(2, $sub2->get_courses_count());
1233  
1234          $courses = $DB->get_records('course', array('category' => $sub2->id), 'id');
1235          $this->assertEquals(array((int)$course2->id, (int)$course3->id), array_keys($courses));
1236  
1237          // Move the courses in sub category 2 back into to sub category 1.
1238          $this->assertTrue(
1239              \core_course\management\helper::move_courses_into_category($sub1->id, array($course2->id, $course3->id))
1240          );
1241  
1242          $this->assertEquals(1, $cat1->get_courses_count());
1243          $this->assertEquals(1, $cat2->get_courses_count());
1244          $this->assertEquals(2, $sub1->get_courses_count());
1245          $this->assertEquals(0, $sub2->get_courses_count());
1246  
1247          $courses = $DB->get_records('course', array('category' => $sub1->id), 'id');
1248          $this->assertEquals(array((int)$course2->id, (int)$course3->id), array_keys($courses));
1249  
1250          // Try moving just one course.
1251          $this->assertTrue(
1252              \core_course\management\helper::move_courses_into_category($sub2->id, $course4->id)
1253          );
1254          $this->assertEquals(1, $cat1->get_courses_count());
1255          $this->assertEquals(0, $cat2->get_courses_count());
1256          $this->assertEquals(2, $sub1->get_courses_count());
1257          $this->assertEquals(1, $sub2->get_courses_count());
1258          $courses = $DB->get_records('course', array('category' => $sub2->id), 'id');
1259          $this->assertEquals(array((int)$course4->id), array_keys($courses));
1260  
1261          // Current state:
1262          // * $cat1 => $course1
1263          //    * $sub1 => $course2, $course3
1264          //    * $sub2 => $course4
1265          // * $cat2 =>.
1266  
1267          // Prevent the user from being able to move into $sub2.
1268          $sub2cap = course_capability_assignment::prohibit(self::CATEGORY_MANAGE, $roleid, $sub2->get_context()->id);
1269          $sub2 = core_course_category::get($sub2->id);
1270          // Suppress debugging messages for a moment.
1271          $olddebug = $CFG->debug;
1272          $CFG->debug = 0;
1273  
1274          // Try to move a course into sub2. This shouldn't be possible because you should always be able to undo what you've done.
1275          // Try moving just one course.
1276          try {
1277              \core_course\management\helper::move_courses_into_category($sub2->id, array($course2->id));
1278              $this->fail('Invalid move of course between categories, action can\'t be undone.');
1279          } catch (\moodle_exception $ex) {
1280              $this->assertEquals(get_string('cannotmovecourses', 'error'), $ex->getMessage());
1281          }
1282          // Nothing should have changed.
1283          $this->assertEquals(1, $cat1->get_courses_count());
1284          $this->assertEquals(0, $cat2->get_courses_count());
1285          $this->assertEquals(2, $sub1->get_courses_count());
1286          $this->assertEquals(1, $sub2->get_courses_count());
1287  
1288          // Now try moving a course out of sub2. Again should not be possible.
1289          // Try to move a course into sub2. This shouldn't be possible because you should always be able to undo what you've done.
1290          // Try moving just one course.
1291          try {
1292              \core_course\management\helper::move_courses_into_category($cat2->id, array($course4->id));
1293              $this->fail('Invalid move of course between categories, action can\'t be undone.');
1294          } catch (\moodle_exception $ex) {
1295              $this->assertEquals(get_string('cannotmovecourses', 'error'), $ex->getMessage());
1296          }
1297          // Nothing should have changed.
1298          $this->assertEquals(1, $cat1->get_courses_count());
1299          $this->assertEquals(0, $cat2->get_courses_count());
1300          $this->assertEquals(2, $sub1->get_courses_count());
1301          $this->assertEquals(1, $sub2->get_courses_count());
1302  
1303          $CFG->debug = $olddebug;
1304      }
1305  
1306  }