diff --git a/admin/roles/assign.php b/admin/roles/assign.php index b7ada55b3da..daa5d260f55 100755 --- a/admin/roles/assign.php +++ b/admin/roles/assign.php @@ -57,6 +57,7 @@ print_error('wrongcontextid', 'error'); } $isfrontpage = $context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID; + $PAGE->set_context($context); $contextname = print_context_name($context); $inmeta = 0; diff --git a/course/category.php b/course/category.php index 7129a805b7e..73f88ccbe52 100644 --- a/course/category.php +++ b/course/category.php @@ -29,13 +29,10 @@ print_error("unknowcategory"); } - if (!$context = get_context_instance(CONTEXT_COURSECAT, $id)) { - print_error("unknowcategory"); - } + $PAGE->set_category_by_id($id); + $context = $PAGE->context; + $category = $PAGE->category; - if (!$category = $DB->get_record("course_categories", array("id"=>$id))) { - print_error("unknowcategory"); - } if (!$category->visible) { require_capability('moodle/category:viewhiddencategories', $context); } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 4c819c1d288..532ef40b620 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -1914,7 +1914,7 @@ function get_login_url($loginguest=false) { * in order to keep redirects working properly. MDL-14495 */ function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsurltome=true) { - global $CFG, $SESSION, $USER, $COURSE, $FULLME, $PAGE, $DB; + global $CFG, $SESSION, $USER, $COURSE, $FULLME, $PAGE, $SITE, $DB; /// setup global $COURSE, themes, language and locale if (!empty($courseorid)) { diff --git a/lib/pagelib.php b/lib/pagelib.php index e511ec0d490..4995bb32604 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -56,6 +56,17 @@ class moodle_page { protected $_context = null; + /** + * This holds any categories that $_course belongs to, starting with the + * particular category it belongs to, and working out through any parent + * categories to the top level. These are loaded progressively, if neaded. + * There are three states. $_categories = null initially when nothing is + * loaded; $_categories = array($id => $cat, $parentid => null) when we have + * loaded $_course->category, but not any parents; and a complete array once + * everything is loaded. + */ + protected $_categories = null; + protected $_bodyclasses = array(); protected $_pagetype = null; @@ -98,6 +109,30 @@ class moodle_page { return $this->_course; } + /** + * @return mixed the category that the page course belongs to. If there isn't one + * (that is, if this is the front page course) returns null. + */ + public function get_category() { + $this->ensure_category_loaded(); + if (!empty($this->_categories)) { + return reset($this->_categories); + } else { + return null; + } + } + + /** + * @return array an array of all the categories the page course belongs to, + * starting with the immediately containing category, and working out to + * the top-level category. This may be the empty array if we are in the + * front page course. + */ + public function get_categories() { + $this->ensure_categories_loaded(); + return $this->_categories; + } + /** * @return object the main context to which this page belongs. */ @@ -175,7 +210,7 @@ class moodle_page { * @param object the course to set as the global course. */ public function set_course($course) { - global $COURSE, $SITE; + global $COURSE; if (empty($course->id)) { throw new coding_exception('$course passed to moodle_page::set_course does not look like a proper course object.'); @@ -192,6 +227,8 @@ class moodle_page { $this->set_context(get_context_instance(CONTEXT_COURSE, $this->_course->id)); } + $this->_categories = null; + moodle_setlocale(); theme_setup(); } @@ -235,6 +272,27 @@ class moodle_page { } } + /** + * Set the course category this page belongs to manually. This automatically + * sets $PAGE->course to be the site coures. You cannot use this method if + * you have already set $PAGE->course - in that case, the category must be + * the one that the course belongs to. This also automatically sets the + * page context to the category context. + * @param integer $categoryid The id of the category to set. + */ + public function set_category_by_id($categoryid) { + global $SITE, $DB; + if (!is_null($this->_course)) { + throw new coding_exception('Attempt to manually set the course category when the course has been set. This is not allowed.'); + } + if (is_array($this->_categories)) { + throw new coding_exception('Course category has already been set. You are not allowed to change it.'); + } + $this->set_course($SITE); + $this->load_category($categoryid); + $this->set_context(get_context_instance(CONTEXT_COURSECAT, $categoryid)); + } + /// Initialisation methods ===================================================== /// These set various things up in a default way. @@ -309,6 +367,48 @@ class moodle_page { } } + protected function ensure_category_loaded() { + if (is_array($this->_categories)) { + return; // Already done. + } + if (is_null($this->_course)) { + throw new coding_exception('Attempt to get the course category for this page before the course was set.'); + } + if ($this->_course->category == 0) { + $this->_categories = array(); + } else { + $this->load_category($this->_course->category); + } + } + + protected function load_category($categoryid) { + global $DB; + $category = $DB->get_record('course_categories', array('id' => $categoryid)); + if (!$category) { + throw new moodle_exception('unknowncategory'); + } + $this->_categories[$category->id] = $category; + $parentcategoryids = explode('/', trim($category->path, '/')); + array_pop($parentcategoryids); + foreach (array_reverse($parentcategoryids) as $catid) { + $this->_categories[$catid] = null; + } + } + + protected function ensure_categories_loaded() { + global $DB; + $this->ensure_category_loaded(); + if (!is_null(end($this->_categories))) { + return; // Already done. + } + $idstoload = array_keys($this->_categories); + array_shift($idstoload); + $categories = $DB->get_records_list('course_categories', 'id', $idstoload); + foreach ($idstoload as $catid) { + $this->_categories[$catid] = $categories[$catid]; + } + } + protected function url_to_class_name($url) { $bits = parse_url($url); $class = str_replace('.', '-', $bits['host']); diff --git a/lib/simpletest/testpagelib_moodlepage.php b/lib/simpletest/testpagelib_moodlepage.php index 623c6f521cc..cc798a0362a 100644 --- a/lib/simpletest/testpagelib_moodlepage.php +++ b/lib/simpletest/testpagelib_moodlepage.php @@ -124,6 +124,26 @@ class moodle_page_test extends UnitTestCase { $this->testpage->set_course($course); } + public function test_cannot_set_category_once_output_started() { + // Setup fixture + $this->testpage->set_state(moodle_page::STATE_PRINTING_HEADER); + // Set expectation. + $this->expectException(); + // Exercise SUT + $this->testpage->set_category_by_id(123); + } + + public function test_cannot_set_category_once_course_set() { + // Setup fixture + $course = $this->create_a_course(); + $this->testpage->set_context(new stdClass); // Avoid trying to set the context. + $this->testpage->set_course($course); + // Set expectation. + $this->expectException(); + // Exercise SUT + $this->testpage->set_category_by_id(123); + } + public function test_set_state_normal_path() { $this->assertEqual(moodle_page::STATE_BEFORE_HEADER, $this->testpage->state); @@ -230,14 +250,15 @@ class moodle_page_test extends UnitTestCase { } /** - * Test functions that affect filter_active table with contextid = $syscontextid. + * Test functions that rely on the context table. */ -class moodle_page_with_db_test extends UnitTestCaseUsingDatabase { +class moodle_page_with_context_table_test extends UnitTestCaseUsingDatabase { protected $testpage; protected $originalcourse; public function setUp() { global $COURSE; + parent::setUp(); $this->originalcourse = $COURSE; $this->testpage = new moodle_page(); $this->create_test_table('context', 'lib'); @@ -248,6 +269,7 @@ class moodle_page_with_db_test extends UnitTestCaseUsingDatabase { global $COURSE; $this->testpage = NULL; $COURSE = $this->originalcourse; + parent::tearDown(); } /** Creates an object with all the fields you would expect a $course object to have. */ @@ -281,4 +303,90 @@ class moodle_page_with_db_test extends UnitTestCaseUsingDatabase { $this->assert(new CheckSpecifiedFieldsExpectation($expectedcontext), $this->testpage->context); } } + +/** + * Test functions that rely on the context table. + */ +class moodle_page_categories_test extends UnitTestCaseUsingDatabase { + protected $testpage; + protected $originalcourse; + + public function setUp() { + global $COURSE, $SITE; + parent::setUp(); + $this->originalcourse = $COURSE; + $this->testpage = new moodle_page(); + $this->create_test_tables(array('course_categories', 'context'), 'lib'); + $this->switch_to_test_db(); + + $context = new stdClass; + $context->contextlevel = CONTEXT_COURSE; + $context->instanceid = $SITE->id; + $context->path = 'not initialised'; + $context->depth = '-1'; + $this->testdb->insert_record('context', $context); + } + + public function tearDown() { + global $COURSE; + $this->testpage = NULL; + $COURSE = $this->originalcourse; + parent::tearDown(); + } + + /** Creates an object with all the fields you would expect a $course object to have. */ + protected function create_a_category_with_context($parentid = 0) { + if ($parentid) { + $parent = $this->testdb->get_record('course_categories', array('id' => $parentid)); + } else { + $parent = new stdClass; + $parent->depth = 0; + $parent->path = ''; + } + $cat = new stdClass; + $cat->name = 'Anonymous test category'; + $cat->description = ''; + $cat->parent = $parentid; + $cat->depth = $parent->depth + 1; + $cat->id = $this->testdb->insert_record('course_categories', $cat); + $cat->path = $parent->path . '/' . $cat->id; + $this->testdb->set_field('course_categories', 'path', $cat->path, array('id' => $cat->id)); + + $context = new stdClass; + $context->contextlevel = CONTEXT_COURSECAT; + $context->instanceid = $cat->id; + $context->path = 'not initialised'; + $context->depth = '-1'; + $this->testdb->insert_record('context', $context); + + return $cat; + } + + public function test_set_category_top_level() { + // Setup fixture + $cat = $this->create_a_category_with_context(); + // Exercise SUT + $this->testpage->set_category_by_id($cat->id); + // Validate + $this->assert(new CheckSpecifiedFieldsExpectation($cat), $this->testpage->category); + $expectedcontext = new stdClass; // Test it sets the context. + $expectedcontext->contextlevel = CONTEXT_COURSECAT; + $expectedcontext->instanceid = $cat->id; + $this->assert(new CheckSpecifiedFieldsExpectation($expectedcontext), $this->testpage->context); + } + + public function test_set_nested_categories() { + // Setup fixture + $topcat = $this->create_a_category_with_context(); + $subcat = $this->create_a_category_with_context($topcat->id); + // Exercise SUT + $this->testpage->set_category_by_id($subcat->id); + // Validate + $categories = $this->testpage->categories; + $this->assertEqual(2, count($categories)); + $this->assert(new CheckSpecifiedFieldsExpectation($topcat), array_pop($categories)); + $this->assert(new CheckSpecifiedFieldsExpectation($subcat), array_pop($categories)); + } +} + ?>