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   * Course and category management helper class tests.
      19   *
      20   * @package    core_course
      21   * @copyright  2013 Sam Hemelryk
      22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      23   */
      24  
      25  defined('MOODLE_INTERNAL') || die();
      26  
      27  global $CFG;
      28  require_once($CFG->dirroot.'/course/lib.php');
      29  require_once($CFG->dirroot.'/course/tests/fixtures/course_capability_assignment.php');
      30  
      31  /**
      32   * Class core_course_management_helper_test
      33   *
      34   * This class tests \core_course\management\helper
      35   */
      36  class core_course_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  }