diff --git a/blocks/navigation/yui/navigation/navigation.js b/blocks/navigation/yui/navigation/navigation.js index 2189d51eed2..62f417e5ce9 100644 --- a/blocks/navigation/yui/navigation/navigation.js +++ b/blocks/navigation/yui/navigation/navigation.js @@ -74,6 +74,35 @@ var EXPANSIONLIMIT_EVERYTHING = 0, EXPANSIONLIMIT_SECTION = 30, EXPANSIONLIMIT_ACTIVITY = 40; +/** + * Mappings for the different types of nodes coming from the navigation. + * Copied from lib/navigationlib.php navigation_node constants. + * @type object + */ +var NODETYPE = { + /** @type int Root node = 0 */ + ROOTNODE : 0, + /** @type int System context = 1 */ + SYSTEM : 1, + /** @type int Course category = 10 */ + CATEGORY : 10, + /** @type int Course = 20 */ + COURSE : 20, + /** @type int Course section = 30 */ + SECTION : 30, + /** @type int Activity (course module) = 40 */ + ACTIVITY : 40, + /** @type int Resource (course module = 50 */ + RESOURCE : 50, + /** @type int Custom node (could be anything) = 60 */ + CUSTOM : 60, + /** @type int Setting = 70 */ + SETTING : 70, + /** @type int User context = 80 */ + USER : 80, + /** @type int Container = 90 */ + CONTAINER : 90 +} /** * Navigation tree class. @@ -299,7 +328,7 @@ BRANCH.prototype = { // Prepare the icon, should be an object representing a pix_icon var branchicon = false; var icon = this.get('icon'); - if (icon && (!isbranch || this.get('type') == 40)) { + if (icon && (!isbranch || this.get('type') == NODETYPE.ACTIVITY)) { branchicon = Y.Node.create(''); branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component)); branchli.addClass('item_with_icon'); @@ -419,13 +448,13 @@ BRANCH.prototype = { var coursecount = 0; for (var i in object.children) { if (typeof(object.children[i])=='object') { - if (object.children[i].type == 20) { + if (object.children[i].type == NODETYPE.COURSE) { coursecount++; } this.addChild(object.children[i]); } } - if (this.get('type') == 10 && coursecount >= M.block_navigation.courselimit) { + if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE) && coursecount >= M.block_navigation.courselimit) { this.addViewAllCoursesChild(this); } this.get('tree').toggleExpansion({target:this.node}); @@ -450,14 +479,14 @@ BRANCH.prototype = { var count = 0, i, children = branch.get('children'); for (i in children) { // Add each branch to the tree - if (children[i].type == 20) { + if (children[i].type == NODETYPE.COURSE) { count++; } if (typeof(children[i])=='object') { branch.addChild(children[i]); } } - if (branch.get('type') == 10 && count >= M.block_navigation.courselimit) { + if (branch.get('type') == NODETYPE.CATEGORY && count >= M.block_navigation.courselimit) { this.addViewAllCoursesChild(branch); } } @@ -468,10 +497,20 @@ BRANCH.prototype = { * Add a link to view all courses in a category */ addViewAllCoursesChild: function(branch) { + var url = null; + if (branch.get('type') == NODETYPE.ROOTNODE) { + if (branch.get('key') === 'mycourses') { + url = M.cfg.wwwroot + '/my'; + } else { + url = M.cfg.wwwroot + '/course/index.php'; + } + } else { + url = M.cfg.wwwroot+'/course/category.php?id=' + branch.get('key'); + } branch.addChild({ name : M.str.moodle.viewallcourses, title : M.str.moodle.viewallcourses, - link : M.cfg.wwwroot+'/course/category.php?id='+branch.get('key'), + link : url, haschildren : false, icon : {'pix':"i/navigationitem",'component':'moodle'} }); diff --git a/enrol/tests/enrollib_test.php b/enrol/tests/enrollib_test.php new file mode 100644 index 00000000000..edbb97ee8b9 --- /dev/null +++ b/enrol/tests/enrollib_test.php @@ -0,0 +1,253 @@ +. + +/** + * Test non-plugin enrollib parts. + * + * @package core + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Test non-plugin enrollib parts. + * + * @package core + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_enrol_testcase extends advanced_testcase { + + public function test_enrol_get_all_users_courses() { + global $DB, $CFG; + + $this->resetAfterTest(); + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + + $admin = get_admin(); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + $user5 = $this->getDataGenerator()->create_user(); + + $category1 = $this->getDataGenerator()->create_category(array('visible'=>0)); + $category2 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category'=>$category1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category'=>$category2->id)); + $course3 = $this->getDataGenerator()->create_course(array('category'=>$category2->id, 'visible'=>0)); + $course4 = $this->getDataGenerator()->create_course(array('category'=>$category2->id)); + + $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $DB->set_field('enrol', 'status', ENROL_INSTANCE_DISABLED, array('id'=>$maninstance1->id)); + $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance4 = $DB->get_record('enrol', array('courseid'=>$course4->id, 'enrol'=>'manual'), '*', MUST_EXIST); + + $manual = enrol_get_plugin('manual'); + $this->assertNotEmpty($manual); + + $manual->enrol_user($maninstance1, $user1->id, $teacherrole->id); + $manual->enrol_user($maninstance1, $user2->id, $studentrole->id); + $manual->enrol_user($maninstance1, $user4->id, $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED); + $manual->enrol_user($maninstance1, $admin->id, $studentrole->id); + + $manual->enrol_user($maninstance2, $user1->id); + $manual->enrol_user($maninstance2, $user2->id); + $manual->enrol_user($maninstance2, $user3->id, 0, 1, time()+(60*60)); + + $manual->enrol_user($maninstance3, $user1->id); + $manual->enrol_user($maninstance3, $user2->id); + $manual->enrol_user($maninstance3, $user3->id, 0, 1, time()-(60*60)); + $manual->enrol_user($maninstance3, $user4->id, 0, 0, 0, ENROL_USER_SUSPENDED); + + + $courses = enrol_get_all_users_courses($CFG->siteguest); + $this->assertSame(array(), $courses); + + $courses = enrol_get_all_users_courses(0); + $this->assertSame(array(), $courses); + + // Results are sorted by visibility, sortorder by default (in our case order of creation) + + $courses = enrol_get_all_users_courses($admin->id); + $this->assertCount(1, $courses); + $this->assertEquals(array($course1->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($admin->id, true); + $this->assertCount(0, $courses); + $this->assertEquals(array(), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user1->id); + $this->assertCount(3, $courses); + $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user1->id, true); + $this->assertCount(2, $courses); + $this->assertEquals(array($course2->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user2->id); + $this->assertCount(3, $courses); + $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user2->id, true); + $this->assertCount(2, $courses); + $this->assertEquals(array($course2->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user3->id); + $this->assertCount(2, $courses); + $this->assertEquals(array($course2->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user3->id, true); + $this->assertCount(1, $courses); + $this->assertEquals(array($course2->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user4->id); + $this->assertCount(2, $courses); + $this->assertEquals(array($course1->id, $course3->id), array_keys($courses)); + + $courses = enrol_get_all_users_courses($user4->id, true); + $this->assertCount(0, $courses); + $this->assertEquals(array(), array_keys($courses)); + + // Make sure sorting and columns work. + + $basefields = array('id', 'category', 'sortorder', 'shortname', 'fullname', 'idnumber', + 'startdate', 'visible', 'groupmode', 'groupmodeforce'); + + $courses = enrol_get_all_users_courses($user2->id, true); + $course = reset($courses); + context_helper::preload_from_record($course); + $course = (array)$course; + $this->assertEquals($basefields, array_keys($course), '', 0, 10, true); + + $courses = enrol_get_all_users_courses($user2->id, false, 'modinfo'); + $course = reset($courses); + $this->assertTrue(property_exists($course, 'modinfo')); + + $courses = enrol_get_all_users_courses($user2->id, false, null, 'id DESC'); + $this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses)); + } + + public function test_enrol_user_sees_own_courses() { + global $DB, $CFG; + + $this->resetAfterTest(); + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + + $admin = get_admin(); + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + $user5 = $this->getDataGenerator()->create_user(); + $user6 = $this->getDataGenerator()->create_user(); + + $category1 = $this->getDataGenerator()->create_category(array('visible'=>0)); + $category2 = $this->getDataGenerator()->create_category(); + $course1 = $this->getDataGenerator()->create_course(array('category'=>$category1->id)); + $course2 = $this->getDataGenerator()->create_course(array('category'=>$category2->id)); + $course3 = $this->getDataGenerator()->create_course(array('category'=>$category2->id, 'visible'=>0)); + $course4 = $this->getDataGenerator()->create_course(array('category'=>$category2->id)); + + $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $DB->set_field('enrol', 'status', ENROL_INSTANCE_DISABLED, array('id'=>$maninstance1->id)); + $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'manual'), '*', MUST_EXIST); + $maninstance4 = $DB->get_record('enrol', array('courseid'=>$course4->id, 'enrol'=>'manual'), '*', MUST_EXIST); + + $manual = enrol_get_plugin('manual'); + $this->assertNotEmpty($manual); + + $manual->enrol_user($maninstance1, $admin->id, $studentrole->id); + + $manual->enrol_user($maninstance3, $user1->id, $teacherrole->id); + + $manual->enrol_user($maninstance2, $user2->id, $studentrole->id); + + $manual->enrol_user($maninstance1, $user3->id, $studentrole->id, 1, time()+(60*60)); + $manual->enrol_user($maninstance2, $user3->id, 0, 1, time()-(60*60)); + $manual->enrol_user($maninstance3, $user2->id, $studentrole->id); + $manual->enrol_user($maninstance4, $user2->id, 0, 0, 0, ENROL_USER_SUSPENDED); + + $manual->enrol_user($maninstance1, $user4->id, $teacherrole->id, 0, 0, ENROL_USER_SUSPENDED); + $manual->enrol_user($maninstance3, $user4->id, 0, 0, 0, ENROL_USER_SUSPENDED); + + + $this->assertFalse(enrol_user_sees_own_courses($CFG->siteguest)); + $this->assertFalse(enrol_user_sees_own_courses(0)); + $this->assertFalse(enrol_user_sees_own_courses($admin)); + $this->assertFalse(enrol_user_sees_own_courses(-222)); // Nonexistent user. + + $this->assertTrue(enrol_user_sees_own_courses($user1)); + $this->assertTrue(enrol_user_sees_own_courses($user2->id)); + $this->assertFalse(enrol_user_sees_own_courses($user3->id)); + $this->assertFalse(enrol_user_sees_own_courses($user4)); + $this->assertFalse(enrol_user_sees_own_courses($user5)); + + $this->setAdminUser(); + $this->assertFalse(enrol_user_sees_own_courses()); + + $this->setGuestUser(); + $this->assertFalse(enrol_user_sees_own_courses()); + + $this->setUser(0); + $this->assertFalse(enrol_user_sees_own_courses()); + + $this->setUser($user1); + $this->assertTrue(enrol_user_sees_own_courses()); + + $this->setUser($user2); + $this->assertTrue(enrol_user_sees_own_courses()); + + $this->setUser($user3); + $this->assertFalse(enrol_user_sees_own_courses()); + + $this->setUser($user4); + $this->assertFalse(enrol_user_sees_own_courses()); + + $this->setUser($user5); + $this->assertFalse(enrol_user_sees_own_courses()); + + $user1 = $DB->get_record('user', array('id'=>$user1->id)); + $this->setUser($user1); + $reads = $DB->perf_get_reads(); + $this->assertTrue(enrol_user_sees_own_courses()); + $this->assertGreaterThan($reads, $DB->perf_get_reads()); + + $user1 = $DB->get_record('user', array('id'=>$user1->id)); + $this->setUser($user1); + require_login($course3); + $reads = $DB->perf_get_reads(); + $this->assertTrue(enrol_user_sees_own_courses()); + $this->assertEquals($reads, $DB->perf_get_reads()); + } +} diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 118b4344b98..75faf34febe 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -392,6 +392,7 @@ $string['creatinguserroles'] = 'Creating user level role assignments and overrid $string['creatingusers'] = 'Creating users'; $string['creatingxmlfile'] = 'Creating XML file'; $string['currency'] = 'Currency'; +$string['currentcourse'] = 'Current course'; $string['currentcourseadding'] = 'Current course, adding data to it'; $string['currentcoursedeleting'] = 'Current course, deleting it first'; $string['currentlanguage'] = 'Current language'; diff --git a/lib/ajax/getnavbranch.php b/lib/ajax/getnavbranch.php index f4edede1f33..e42d63a74a1 100644 --- a/lib/ajax/getnavbranch.php +++ b/lib/ajax/getnavbranch.php @@ -35,8 +35,10 @@ require_once($CFG->dirroot.'/course/lib.php'); try { // Start buffer capture so that we can `remove` any errors ob_start(); - // Require id This is the key for whatever branch we want to get - $branchid = required_param('id', PARAM_INT); + // Require id This is the key for whatever branch we want to get. + // This accepts alphanum because the courses and my courses branches don't have numerical keys. + // For those branches we return the alphanum key, courses and mycourses. + $branchid = required_param('id', PARAM_ALPHANUM); // This identifies the type of the branch we want to get $branchtype = required_param('type', PARAM_INT); // This identifies the block instance requesting AJAX extension @@ -87,8 +89,14 @@ try { } $converter = new navigation_json(); - // Find the actuall branch we are looking for - $branch = $navigation->find($branchid, $branchtype); + // Find the actual branch we are looking for + if ($branchtype != 0) { + $branch = $navigation->find($branchid, $branchtype); + } else if ($branchid === 'mycourses' || $branchid === 'courses') { + $branch = $navigation->find($branchid, navigation_node::TYPE_ROOTNODE); + } else { + throw new coding_exception('Invalid branch type/id passed to AJAX call to load branches.'); + } // Remove links to categories if required. if (!$linkcategories) { diff --git a/lib/enrollib.php b/lib/enrollib.php index c86dd231469..a045d77e7d2 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -710,6 +710,56 @@ function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $ } +/** + * Can user access at least one enrolled course? + * + * Cheat if necessary, but find out as fast as possible! + * + * @param int|stdClass $user null means use current user + * @return bool + */ +function enrol_user_sees_own_courses($user = null) { + global $USER; + + if ($user === null) { + $user = $USER; + } + $userid = is_object($user) ? $user->id : $user; + + // Guest account does not have any courses + if (isguestuser($userid) or empty($userid)) { + return false; + } + + // Let's cheat here if this is the current user, + // if user accessed any course recently, then most probably + // we do not need to query the database at all. + if ($USER->id == $userid) { + if (!empty($USER->enrol['enrolled'])) { + foreach ($USER->enrol['enrolled'] as $until) { + if ($until > time()) { + return true; + } + } + } + } + + // Now the slow way. + $courses = enrol_get_all_users_courses($userid, true); + foreach($courses as $course) { + if ($course->visible) { + return true; + } + context_helper::preload_from_record($course); + $context = context_course::instance($course->id); + if (has_capability('moodle/course:viewhiddencourses', $context, $user)) { + return true; + } + } + + return false; +} + /** * Returns list of courses user is enrolled into without any capability checks * - $fields is an array of fieldnames to ADD diff --git a/lib/navigationlib.php b/lib/navigationlib.php index 3ebac4dda3b..e124c920e6a 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -73,6 +73,12 @@ class navigation_node implements renderable { const TYPE_USER = 80; /** @var int Setting node type, used for containers of no importance 90 */ const TYPE_CONTAINER = 90; + /** var int Course the current user is not enrolled in */ + const COURSE_OTHER = 0; + /** var int Course the current user is enrolled in but not viewing */ + const COURSE_MY = 1; + /** var int Course the current user is currently viewing */ + const COURSE_CURRENT = 2; /** @var int Parameter to aid the coder in tracking [optional] */ public $id = null; @@ -118,6 +124,8 @@ class navigation_node implements renderable { public $parent = null; /** @var bool Override to not display the icon even if one is provided **/ public $hideicon = false; + /** @var bool Set to true if we KNOW that this node can be expanded. */ + public $isexpandable = false; /** @var array */ protected $namedtypes = array(0=>'system',10=>'category',20=>'course',30=>'structure',40=>'activity',50=>'resource',60=>'custom',70=>'setting', 80=>'user'); /** @var moodle_url */ @@ -379,7 +387,7 @@ class navigation_node implements renderable { * @return bool Returns true if it has children or could have (by AJAX expansion) */ public function has_children() { - return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0); + return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0 || $this->isexpandable); } /** @@ -580,7 +588,7 @@ class navigation_node implements renderable { */ public function find_expandable(array &$expandable) { foreach ($this->children as &$child) { - if ($child->nodetype == self::NODETYPE_BRANCH && $child->children->count() == 0 && $child->display) { + if ($child->display && $child->has_children() && $child->children->count() == 0) { $child->id = 'expandable_branch_'.(count($expandable)+1); $this->add_class('canexpand'); $expandable[] = array('id' => $child->id, 'key' => $child->key, 'type' => $child->type); @@ -935,6 +943,8 @@ class global_navigation extends navigation_node { protected $showemptysections = true; /** @var bool A switch for whether courses should be shown within categories on the navigation. */ protected $showcategories = null; + /** @var null@var bool A switch for whether or not to show categories in the my courses branch. */ + protected $showmycategories = null; /** @var array An array of stdClasses for users that the navigation is extended for */ protected $extendforuser = array(); /** @var navigation_cache */ @@ -1020,8 +1030,8 @@ class global_navigation extends navigation_node { * @return bool */ public function initialise() { - global $CFG, $SITE, $USER, $DB; - // Check if it has alread been initialised + global $CFG, $SITE, $USER; + // Check if it has already been initialised if ($this->initialised || during_initial_install()) { return true; } @@ -1029,11 +1039,12 @@ class global_navigation extends navigation_node { // Set up the five base root nodes. These are nodes where we will put our // content and are as follows: - // site: Navigation for the front page. - // myprofile: User profile information goes here. - // mycourses: The users courses get added here. - // courses: Additional courses are added here. - // users: Other users information loaded here. + // site: Navigation for the front page. + // myprofile: User profile information goes here. + // currentcourse: The course being currently viewed. + // mycourses: The users courses get added here. + // courses: Additional courses are added here. + // users: Other users information loaded here. $this->rootnodes = array(); if (get_home_page() == HOMEPAGE_SITE) { // The home element should be my moodle because the root element is the site @@ -1048,141 +1059,34 @@ class global_navigation extends navigation_node { $this->rootnodes['home']->action->param('redirect', '0'); } } - $this->rootnodes['site'] = $this->add_course($SITE); + $this->rootnodes['site'] = $this->add_course($SITE); $this->rootnodes['myprofile'] = $this->add(get_string('myprofile'), null, self::TYPE_USER, null, 'myprofile'); - $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), null, self::TYPE_ROOTNODE, null, 'mycourses'); - $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses'); - $this->rootnodes['users'] = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users'); + $this->rootnodes['currentcourse'] = $this->add(get_string('currentcourse'), null, self::TYPE_ROOTNODE, null, 'currentcourse'); + $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), new moodle_url('/my'), self::TYPE_ROOTNODE, null, 'mycourses'); + $this->rootnodes['courses'] = $this->add(get_string('courses'), new moodle_url('/course/index.php'), self::TYPE_ROOTNODE, null, 'courses'); + $this->rootnodes['users'] = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users'); // We always load the frontpage course to ensure it is available without // JavaScript enabled. $this->add_front_page_course_essentials($this->rootnodes['site'], $SITE); $this->load_course_sections($SITE, $this->rootnodes['site']); - // Fetch all of the users courses. - $mycourses = enrol_get_my_courses(); - // We need to show categories if we can show categories and the user isn't enrolled in any courses or we're not showing all courses - $showcategories = ($this->show_categories() && (count($mycourses) == 0 || !empty($CFG->navshowallcourses))); + $course = $this->page->course; + // $issite gets set to true if the current pages course is the sites frontpage course $issite = ($this->page->course->id == $SITE->id); - // $ismycourse gets set to true if the user is enrolled in the current pages course. - $ismycourse = !$issite && (array_key_exists($this->page->course->id, $mycourses)); + // Determine if the user is enrolled in any course. + $enrolledinanycourse = enrol_user_sees_own_courses(); - // Check if any courses were returned. - if (count($mycourses) > 0) { - - // Check if categories should be displayed within the my courses branch - if (!empty($CFG->navshowmycoursecategories)) { - - // Find the category of each mycourse - $categories = array(); - foreach ($mycourses as $course) { - $categories[] = $course->category; - } - - // Do a single DB query to get the categories immediately associated with - // courses the user is enrolled in. - $categories = $DB->get_records_list('course_categories', 'id', array_unique($categories), 'depth ASC, sortorder ASC'); - // Work out the parent categories that we need to load that we havn't - // already got. - $categoryids = array(); - foreach ($categories as $category) { - $categoryids = array_merge($categoryids, explode('/', trim($category->path, '/'))); - } - $categoryids = array_unique($categoryids); - $categoryids = array_diff($categoryids, array_keys($categories)); - - if (count($categoryids)) { - // Fetch any other categories we need. - $allcategories = $DB->get_records_list('course_categories', 'id', $categoryids, 'depth ASC, sortorder ASC'); - if (is_array($allcategories) && count($allcategories) > 0) { - $categories = array_merge($categories, $allcategories); - } - } - - // We ONLY want the categories, we need to get rid of the keys - $categories = array_values($categories); - $addedcategories = array(); - while (($category = array_shift($categories)) !== null) { - if ($category->parent == '0') { - $categoryparent = $this->rootnodes['mycourses']; - } else if (array_key_exists($category->parent, $addedcategories)) { - $categoryparent = $addedcategories[$category->parent]; - } else { - // Prepare to count iterations. We don't want to loop forever - // accidentally if for some reason a category can't be placed. - if (!isset($category->loopcount)) { - $category->loopcount = 0; - } - $category->loopcount++; - if ($category->loopcount > 5) { - // This is a pretty serious problem and this should never happen. - // If it does then for some reason a category has been loaded but - // its parents have now. It could be data corruption. - debugging('Category '.$category->id.' could not be placed within the navigation', DEBUG_DEVELOPER); - } else { - // Add it back to the end of the categories array - array_push($categories, $category); - } - continue; - } - - $url = new moodle_url('/course/category.php', array('id' => $category->id)); - $addedcategories[$category->id] = $categoryparent->add($category->name, $url, self::TYPE_CATEGORY, $category->name, $category->id); - - if (!$category->visible) { - // Let's decide the context where viewhidden cap checks will happen. - if ($category->parent == '0') { - $contexttocheck = context_system::instance(); - } else { - $contexttocheck = context_coursecat::instance($category->parent); - } - if (!has_capability('moodle/category:viewhiddencategories', $contexttocheck)) { - $addedcategories[$category->id]->display = false; - } else { - $addedcategories[$category->id]->hidden = true; - } - } - } + $this->rootnodes['currentcourse']->mainnavonly = true; + if ($enrolledinanycourse) { + $this->rootnodes['mycourses']->isexpandable = true; + if ($CFG->navshowallcourses) { + // When we show all courses we need to show both the my courses and the regular courses branch. + $this->rootnodes['courses']->isexpandable = true; } - - // Add all of the users courses to the navigation. - // First up we need to add to the mycourses section. - foreach ($mycourses as $course) { - $course->coursenode = $this->add_course($course, false, true); - } - - if (!empty($CFG->navshowallcourses)) { - // Load all courses - $this->load_all_courses(); - } - - // Next if nasvshowallcourses is enabled then we need to add courses - // to the courses branch as well. - if (!empty($CFG->navshowallcourses)) { - foreach ($mycourses as $course) { - if (!empty($course->category) && !$this->can_add_more_courses_to_category($course->category)) { - continue; - } - $genericcoursenode = $this->add_course($course, true); - if ($genericcoursenode->isactive) { - // We don't want this node to be active because we want the - // node in the mycourses branch to be active. - $genericcoursenode->make_inactive(); - $genericcoursenode->collapse = true; - if ($genericcoursenode->parent && $genericcoursenode->parent->type == self::TYPE_CATEGORY) { - $parent = $genericcoursenode->parent; - while ($parent && $parent->type == self::TYPE_CATEGORY) { - $parent->collapse = true; - $parent = $parent->parent; - } - } - } - } - } - } else if (!empty($CFG->navshowallcourses) || !$this->show_categories()) { - // Load all courses - $this->load_all_courses(); + } else { + $this->rootnodes['courses']->isexpandable = true; } $canviewcourseprofile = true; @@ -1190,36 +1094,21 @@ class global_navigation extends navigation_node { // Next load context specific content into the navigation switch ($this->page->context->contextlevel) { case CONTEXT_SYSTEM : - // This has already been loaded we just need to map the variable - if ($showcategories) { - $this->load_all_categories(self::LOAD_ROOT_CATEGORIES, true); - } + // Nothing left to do here I feel. break; case CONTEXT_COURSECAT : - // This has already been loaded we just need to map the variable - if ($this->show_categories()) { - $this->load_all_categories($this->page->context->instanceid, true); - } + // This is essential, we must load categories. + $this->load_all_categories($this->page->context->instanceid, true); break; case CONTEXT_BLOCK : case CONTEXT_COURSE : if ($issite) { - // If it is the front page course, or a block on it then - // all we need to do is load the root categories if required - if ($showcategories) { - $this->load_all_categories(self::LOAD_ROOT_CATEGORIES, true); - } + // Nothing left to do here. break; } - // Load the course associated with the page into the navigation - $course = $this->page->course; - if ($this->show_categories() && !$ismycourse) { - // The user isn't enrolled in the course and we need to show categories in which case we need - // to load the category relating to the course and depending up $showcategories all of the root categories as well. - $this->load_all_categories($course->category, $showcategories); - } - $coursenode = $this->load_course($course); + // Load the course associated with the current page into the navigation. + $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); // If the course wasn't added then don't try going any further. if (!$coursenode) { $canviewcourseprofile = false; @@ -1231,25 +1120,15 @@ class global_navigation extends navigation_node { // Not enrolled, can't view, and hasn't switched roles if (!can_access_course($course)) { - // TODO: very ugly hack - do not force "parents" to enrol into course their child is enrolled in, + // Very ugly hack - do not force "parents" to enrol into course their child is enrolled in, // this hack has been propagated from user/view.php to display the navigation node. (MDL-25805) - $isparent = false; - if ($this->useridtouseforparentchecks) { - if ($this->useridtouseforparentchecks != $USER->id) { - $usercontext = context_user::instance($this->useridtouseforparentchecks, MUST_EXIST); - if ($DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id)) - and has_capability('moodle/user:viewdetails', $usercontext)) { - $isparent = true; - } - } - } - - if (!$isparent) { + if (!$this->current_user_is_parent_role()) { $coursenode->make_active(); $canviewcourseprofile = false; break; } } + // Add the essentials such as reports etc... $this->add_course_essentials($coursenode, $course); // Extend course navigation with it's sections/activities @@ -1257,6 +1136,7 @@ class global_navigation extends navigation_node { if (!$coursenode->contains_active_node() && !$coursenode->search_for_active_node()) { $coursenode->make_active(); } + break; case CONTEXT_MODULE : if ($issite) { @@ -1274,12 +1154,8 @@ class global_navigation extends navigation_node { $course = $this->page->course; $cm = $this->page->cm; - if ($this->show_categories() && !$ismycourse) { - $this->load_all_categories($course->category, $showcategories); - } - // Load the course associated with the page into the navigation - $coursenode = $this->load_course($course); + $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); // If the course wasn't added then don't try going any further. if (!$coursenode) { @@ -1362,11 +1238,8 @@ class global_navigation extends navigation_node { break; } $course = $this->page->course; - if ($this->show_categories() && !$ismycourse) { - $this->load_all_categories($course->category, $showcategories); - } // Load the course associated with the user into the navigation - $coursenode = $this->load_course($course); + $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); // If the course wasn't added then don't try going any further. if (!$coursenode) { @@ -1386,34 +1259,6 @@ class global_navigation extends navigation_node { break; } - // Look for all categories which have been loaded - if ($showcategories) { - $categories = $this->find_all_of_type(self::TYPE_CATEGORY); - if (count($categories) !== 0) { - $categoryids = array(); - foreach ($categories as $category) { - $categoryids[] = $category->key; - } - list($categoriessql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED); - $params['limit'] = (!empty($CFG->navcourselimit))?$CFG->navcourselimit:20; - $sql = "SELECT cc.id, COUNT(c.id) AS coursecount - FROM {course_categories} cc - JOIN {course} c ON c.category = cc.id - WHERE cc.id {$categoriessql} - GROUP BY cc.id - HAVING COUNT(c.id) > :limit"; - $excessivecategories = $DB->get_records_sql($sql, $params); - foreach ($categories as &$category) { - if (array_key_exists($category->key, $excessivecategories) && !$this->can_add_more_courses_to_category($category)) { - $url = new moodle_url('/course/category.php', array('id'=>$category->key)); - $category->add(get_string('viewallcourses'), $url, self::TYPE_SETTING); - } - } - } - } else if ((!empty($CFG->navshowallcourses) || empty($mycourses)) && !$this->can_add_more_courses_to_category($this->rootnodes['courses'])) { - $this->rootnodes['courses']->add(get_string('viewallcoursescategories'), new moodle_url('/course/index.php'), self::TYPE_SETTING); - } - // Load for the current user $this->load_for_user(); if ($this->page->context->contextlevel >= CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id && $canviewcourseprofile) { @@ -1434,7 +1279,7 @@ class global_navigation extends navigation_node { // This is the preferred function name as there is less chance of conflicts $function($this); } else if (function_exists($oldfunction)) { - // We continue to support the old function name to ensure backwards compatability + // We continue to support the old function name to ensure backwards compatibility debugging("Deprecated local plugin navigation callback: Please rename '{$oldfunction}' to '{$function}'. Support for the old callback will be dropped after the release of 2.4", DEBUG_DEVELOPER); $oldfunction($this); } @@ -1470,20 +1315,64 @@ class global_navigation extends navigation_node { } /** - * Returns true if courses should be shown within categories on the navigation. + * Returns true if the current user is a parent of the user being currently viewed. * + * If the current user is not viewing another user, or if the current user does not hold any parent roles over the + * other user being viewed this function returns false. + * In order to set the user for whom we are checking against you must call {@link set_userid_for_parent_checks()} + * + * @since 2.4 * @return bool */ - protected function show_categories() { + protected function current_user_is_parent_role() { + global $USER, $DB; + if ($this->useridtouseforparentchecks && $this->useridtouseforparentchecks != $USER->id) { + $usercontext = context_user::instance($this->useridtouseforparentchecks, MUST_EXIST); + if (!has_capability('moodle/user:viewdetails', $usercontext)) { + return false; + } + if ($DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id))) { + return true; + } + } + return false; + } + + /** + * Returns true if courses should be shown within categories on the navigation. + * + * @param bool $ismycourse Set to true if you are calculating this for a course. + * @return bool + */ + protected function show_categories($ismycourse = false) { global $CFG, $DB; + if ($ismycourse) { + return $this->show_my_categories(); + } if ($this->showcategories === null) { - $show = $this->page->context->contextlevel == CONTEXT_COURSECAT; - $show = $show || (!empty($CFG->navshowcategories) && $DB->count_records('course_categories') > 1); + $show = false; + if ($this->page->context->contextlevel == CONTEXT_COURSECAT) { + $show = true; + } else if (!empty($CFG->navshowcategories) && $DB->count_records('course_categories') > 1) { + $show = true; + } $this->showcategories = $show; } return $this->showcategories; } + /** + * Returns true if we should show categories in the My Courses branch. + * @return bool + */ + protected function show_my_categories() { + global $CFG, $DB; + if ($this->showmycategories === null) { + $this->showmycategories = !empty($CFG->navshowmycoursecategories) && $DB->count_records('course_categories') > 1; + } + return $this->showmycategories; + } + /** * Loads the courses in Moodle into the navigation. * @@ -1638,6 +1527,10 @@ class global_navigation extends navigation_node { // frotpage is not wanted here continue; } + if ($this->page->course && ($this->page->course->id == $course->id)) { + // Don't include the currentcourse in this nodelist - it's displayed in the Current course node + continue; + } context_instance_preload($course); if (!$course->visible && !is_role_switched($course->id) && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) { continue; @@ -1690,7 +1583,7 @@ class global_navigation extends navigation_node { * @return navigation_node|void returns a navigation node if a category has been loaded. */ protected function load_all_categories($categoryid = self::LOAD_ROOT_CATEGORIES, $showbasecategories = false) { - global $DB; + global $CFG, $DB; // Check if this category has already been loaded if ($this->allcategoriesloaded || ($categoryid < 1 && $this->is_category_fully_loaded($categoryid))) { @@ -1808,6 +1701,31 @@ class global_navigation extends navigation_node { $this->load_all_courses($readytoloadcourses); } } + + // Look for all categories which have been loaded + if (!empty($this->addedcategories)) { + $categoryids = array(); + foreach ($this->addedcategories as $category) { + if ($this->can_add_more_courses_to_category($category)) { + $categoryids[] = $category->key; + } + } + list($categoriessql, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED); + $params['limit'] = (!empty($CFG->navcourselimit))?$CFG->navcourselimit:20; + $sql = "SELECT cc.id, COUNT(c.id) AS coursecount + FROM {course_categories} cc + JOIN {course} c ON c.category = cc.id + WHERE cc.id {$categoriessql} + GROUP BY cc.id + HAVING COUNT(c.id) > :limit"; + $excessivecategories = $DB->get_records_sql($sql, $params); + foreach ($categories as &$category) { + if (array_key_exists($category->key, $excessivecategories) && !$this->can_add_more_courses_to_category($category)) { + $url = new moodle_url('/course/category.php', array('id'=>$category->key)); + $category->add(get_string('viewallcourses'), $url, self::TYPE_SETTING); + } + } + } } /** @@ -2178,7 +2096,7 @@ class global_navigation extends navigation_node { $course = $this->page->course; $baseargs = array('id'=>$user->id); if ($course->id != $SITE->id && (!$iscurrentuser || $forceforcontext)) { - $coursenode = $this->load_course($course); + $coursenode = $this->add_course($course, false, self::COURSE_CURRENT); $baseargs['course'] = $course->id; $coursecontext = context_course::instance($course->id); $issitecourse = false; @@ -2438,7 +2356,7 @@ class global_navigation extends navigation_node { * @param bool $ismycourse * @return navigation_node */ - public function add_course(stdClass $course, $forcegeneric = false, $ismycourse = false) { + public function add_course(stdClass $course, $forcegeneric = false, $coursetype = self::COURSE_OTHER) { global $CFG, $SITE; // We found the course... we can return it now :) @@ -2465,7 +2383,10 @@ class global_navigation extends navigation_node { if (empty($CFG->usesitenameforsitepages)) { $shortname = get_string('sitepages'); } - } else if ($ismycourse && !$forcegeneric) { + } else if ($coursetype == self::COURSE_CURRENT) { + $parent = $this->rootnodes['currentcourse']; + $url = new moodle_url('/course/view.php', array('id'=>$course->id)); + } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_CATEGORY))) { // Nothing to do here the above statement set $parent to the category within mycourses. } else { @@ -2475,8 +2396,8 @@ class global_navigation extends navigation_node { } else { $parent = $this->rootnodes['courses']; $url = new moodle_url('/course/view.php', array('id'=>$course->id)); - if (!empty($course->category) && $this->show_categories()) { - if ($this->show_categories() && !$this->is_category_fully_loaded($course->category)) { + if (!empty($course->category) && $this->show_categories($coursetype == self::COURSE_MY)) { + if (!$this->is_category_fully_loaded($course->category)) { // We need to load the category structure for this course $this->load_all_categories($course->category, false); } @@ -2735,6 +2656,9 @@ class global_navigation extends navigation_node { if (!$this->initialised) { $this->initialise(); } + if ($type == self::TYPE_ROOTNODE && array_key_exists($key, $this->rootnodes)) { + return $this->rootnodes[$key]; + } return parent::find($key, $type); } } @@ -2786,7 +2710,7 @@ class global_navigation_for_ajax extends global_navigation { * @return array An array of the expandable nodes */ public function initialise() { - global $CFG, $DB, $SITE; + global $DB, $SITE; if ($this->initialised || during_initial_install()) { return $this->expandable; @@ -2795,10 +2719,18 @@ class global_navigation_for_ajax extends global_navigation { $this->rootnodes = array(); $this->rootnodes['site'] = $this->add_course($SITE); + $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), new moodle_url('/my'), self::TYPE_ROOTNODE, null, 'mycourses'); $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses'); // Branchtype will be one of navigation_node::TYPE_* switch ($this->branchtype) { + case 0: + if ($this->instanceid === 'mycourses') { + $this->load_courses_enrolled(); + } else if ($this->instanceid === 'courses') { + $this->load_courses_other(); + } + break; case self::TYPE_CATEGORY : $this->load_category($this->instanceid); break; @@ -2858,6 +2790,51 @@ class global_navigation_for_ajax extends global_navigation { return $this->expandable; } + /** + * They've expanded the 'my courses' branch. + */ + protected function load_courses_enrolled() { + global $DB; + $courses = enrol_get_my_courses(); + if ($this->show_my_categories(true)) { + // OK Actually we are loading categories. We only want to load categories that have a parent of 0. + // In order to make sure we load everything required we must first find the categories that are not + // base categories and work out the bottom category in thier path. + $categoryids = array(); + foreach ($courses as $course) { + $categoryids[] = $course->category; + } + $categoryids = array_unique($categoryids); + list($sql, $params) = $DB->get_in_or_equal($categoryids); + $categories = $DB->get_recordset_select('course_categories', 'id '.$sql.' AND parent <> 0', $params, 'sortorder, id', 'id, path'); + foreach ($categories as $category) { + $bits = explode('/', trim($category->path,'/')); + $categoryids[] = array_shift($bits); + } + $categoryids = array_unique($categoryids); + $categories->close(); + + // Now we load the base categories. + list($sql, $params) = $DB->get_in_or_equal($categoryids); + $categories = $DB->get_recordset_select('course_categories', 'id '.$sql.' AND parent = 0', $params, 'sortorder, id'); + foreach ($categories as $category) { + $this->add_category($category, $this->rootnodes['mycourses']); + } + $categories->close(); + } else { + foreach ($courses as $course) { + $this->add_course($course, false, self::COURSE_MY); + } + } + } + + /** + * They've expanded the general 'courses' branch. + */ + protected function load_courses_other() { + $this->load_all_courses(); + } + /** * Loads a single category into the AJAX navigation. * @@ -2900,7 +2877,6 @@ class global_navigation_for_ajax extends global_navigation { $categories->close(); if (!is_null($basecategory)) { - //echo "
".print_r($subcategories, true).'
'; foreach ($subcategories as $category) { $this->add_category($category, $basecategory); }