MDL-27809 navigation: Reworked the caching within navigation and improved the orderin...
authorSam Hemelryk <sam@moodle.com>
Fri, 24 Jun 2011 08:42:41 +0000 (16:42 +0800)
committerSam Hemelryk <sam@moodle.com>
Fri, 24 Jun 2011 08:42:41 +0000 (16:42 +0800)
course/switchrole.php
lib/navigationlib.php
lib/simpletest/testnavigationlib.php

index 2a4842b..12cba20 100644 (file)
@@ -33,9 +33,9 @@
 require_once('../config.php');
 require_once($CFG->dirroot.'/course/lib.php');
 
-$id          = required_param('id', PARAM_INT);
-$switchrole  = optional_param('switchrole',-1, PARAM_INT);
-$returnurlkey = optional_param('returnurl', false, PARAM_INT);
+$id         = required_param('id', PARAM_INT);
+$switchrole = optional_param('switchrole',-1, PARAM_INT);
+$returnurl  = optional_param('returnurl', false, PARAM_URL);
 
 $PAGE->set_url('/course/switchrole.php', array('id'=>$id));
 
@@ -69,17 +69,21 @@ if ($switchrole > 0 && has_capability('moodle/role:switchroles', $context)) {
     }
 }
 
-$returnurl = false;
-if ($returnurlkey && !empty($SESSION->returnurl) && strpos($SESSION->returnurl, 'moodle_url')!==false) {
-    $returnurl = @unserialize($SESSION->returnurl);
-    if (!($returnurl instanceof moodle_url)) {
-        $returnurl = false;
+// TODO: Using SESSION->returnurl is deprecated and should be removed in the future.
+// Till then this code remains to support any external applications calling this script.
+if (!empty($returnurl) && is_numeric($returnurl)) {
+    $returnurl = false;
+    if (!empty($SESSION->returnurl) && strpos($SESSION->returnurl, 'moodle_url')!==false) {
+        debugging('Code calling switchrole should be passing a URL as a param.', DEBUG_DEVELOPER);
+        $returnurl = @unserialize($SESSION->returnurl);
+        if (!($returnurl instanceof moodle_url)) {
+            $returnurl = false;
+        }
     }
-    unset($SESSION->returnurl);
 }
 
-if ($returnurl===false) {
-    $returnurl = new moodle_url('/course/view.php', array('id'=>$course->id));
+if ($returnurl === false) {
+    $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
 }
-redirect($returnurl);
 
+redirect($returnurl);
\ No newline at end of file
index 1c24fa2..b091379 100644 (file)
@@ -884,10 +884,14 @@ class navigation_node_collection implements IteratorAggregate {
 
     /**
      * Gets the number of nodes in this collection
+     *
+     * This option uses an internal count rather than counting the actual options to avoid
+     * a performance hit through the count function.
+     *
      * @return int
      */
     public function count() {
-        return count($this->collection);
+        return $this->count;
     }
     /**
      * Gets an array iterator for the collection.
@@ -939,6 +943,8 @@ class global_navigation extends navigation_node {
     protected $cache;
     /** @var array */
     protected $addedcourses = array();
+    /** @var array */
+    protected $addedcategories = array();
     /** @var int */
     protected $expansionlimit = 0;
     /** @var int */
@@ -981,12 +987,6 @@ class global_navigation extends navigation_node {
         $this->page = $page;
         $this->forceopen = true;
         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
-
-        // Check if we need to clear the cache
-        $regenerate = optional_param('regenerate', null, PARAM_TEXT);
-        if ($regenerate === 'navigation') {
-            $this->cache->clear();
-        }
     }
 
     /**
@@ -1033,18 +1033,18 @@ class global_navigation extends navigation_node {
         if (get_home_page() == HOMEPAGE_SITE) {
             // The home element should be my moodle because the root element is the site
             if (isloggedin() && !isguestuser()) {  // Makes no sense if you aren't logged in
-                $this->rootnodes['home']      = $this->add(get_string('myhome'), new moodle_url('/my/'), self::TYPE_SETTING, null, 'home');
+                $this->rootnodes['home'] = $this->add(get_string('myhome'), new moodle_url('/my/'), self::TYPE_SETTING, null, 'home');
             }
         } else {
             // The home element should be the site because the root node is my moodle
-            $this->rootnodes['home']      = $this->add(get_string('sitehome'), new moodle_url('/'), self::TYPE_SETTING, null, 'home');
+            $this->rootnodes['home'] = $this->add(get_string('sitehome'), new moodle_url('/'), self::TYPE_SETTING, null, 'home');
             if ($CFG->defaulthomepage == HOMEPAGE_MY) {
                 // We need to stop automatic redirection
                 $this->rootnodes['home']->action->param('redirect', '0');
             }
         }
         $this->rootnodes['site']      = $this->add_course($SITE);
-        $this->rootnodes['myprofile']   = $this->add(get_string('myprofile'), null, self::TYPE_USER, null, 'myprofile');
+        $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'), null, self::TYPE_ROOTNODE, null, 'courses');
         $this->rootnodes['users']     = $this->add(get_string('users'), null, self::TYPE_ROOTNODE, null, 'users');
@@ -1060,22 +1060,21 @@ class global_navigation extends navigation_node {
             $CFG->navshowcategories = false;
         }
 
-        $this->mycourses = enrol_get_my_courses(NULL, 'visible DESC,sortorder ASC', $limit);
-        $showallcourses = (count($this->mycourses) == 0 || !empty($CFG->navshowallcourses));
+        $mycourses = enrol_get_my_courses(NULL, 'visible DESC,sortorder ASC', $limit);
+        $showallcourses = (count($mycourses) == 0 || !empty($CFG->navshowallcourses));
         $showcategories = ($showallcourses && !empty($CFG->navshowcategories));
+        $issite = ($this->page->course->id != SITEID);
+        $ismycourse = (array_key_exists($this->page->course->id, $mycourses));
 
         // Check if any courses were returned.
-        if (count($this->mycourses) > 0) {
+        if (count($mycourses) > 0) {
             // Add all of the users courses to the navigation
-            foreach ($this->mycourses as &$course) {
-                $course->coursenode = $this->add_course($course);
+            foreach ($mycourses as $course) {
+                $course->coursenode = $this->add_course($course, false, true);
             }
         }
 
-        if ($showcategories) {
-            // Load all categories (ensures we get the base categories)
-            $this->load_all_categories();
-        } else if ($showallcourses) {
+        if ($showallcourses) {
             // Load all courses
             $this->load_all_courses();
         }
@@ -1092,16 +1091,20 @@ class global_navigation extends navigation_node {
             case CONTEXT_SYSTEM :
                 // This has already been loaded we just need to map the variable
                 $coursenode = $frontpagecourse;
+                $this->load_all_categories(null, $showcategories);
                 break;
             case CONTEXT_COURSECAT :
                 // This has already been loaded we just need to map the variable
                 $coursenode = $frontpagecourse;
-                $this->load_all_categories($this->page->context->instanceid);
+                $this->load_all_categories($this->page->context->instanceid, $showcategories);
                 break;
             case CONTEXT_BLOCK :
             case CONTEXT_COURSE :
                 // Load the course associated with the page into the navigation
                 $course = $this->page->course;
+                if ($showcategories && !$issite && !$ismycourse) {
+                    $this->load_all_categories($course->category, $showcategories);
+                }
                 $coursenode = $this->load_course($course);
 
                 // If the course wasn't added then don't try going any further.
@@ -1120,10 +1123,9 @@ class global_navigation extends navigation_node {
                     // this hack has been propagated from user/view.php to display the navigation node. (MDL-25805)
                     $isparent = false;
                     if ($this->useridtouseforparentchecks) {
-                        $currentuser = ($this->useridtouseforparentchecks == $USER->id);
-                        if (!$currentuser) {
+                        if ($this->useridtouseforparentchecks != $USER->id) {
                             $usercontext   = get_context_instance(CONTEXT_USER, $this->useridtouseforparentchecks, MUST_EXIST);
-                            if ($DB->record_exists('role_assignments', array('userid'=>$USER->id, 'contextid'=>$usercontext->id))
+                            if ($DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id))
                                     and has_capability('moodle/user:viewdetails', $usercontext)) {
                                 $isparent = true;
                             }
@@ -1149,6 +1151,11 @@ class global_navigation extends navigation_node {
             case CONTEXT_MODULE :
                 $course = $this->page->course;
                 $cm = $this->page->cm;
+
+                if ($showcategories && !$issite && !$ismycourse) {
+                    $this->load_all_categories($course->category, $showcategories);
+                }
+
                 // Load the course associated with the page into the navigation
                 $coursenode = $this->load_course($course);
 
@@ -1182,7 +1189,8 @@ class global_navigation extends navigation_node {
 
                     // Load all of the section activities for the section the cm belongs to.
                     if (isset($cm->sectionnumber) and !empty($sections[$cm->sectionnumber])) {
-                        $activities = $this->load_section_activities($sections[$cm->sectionnumber]->sectionnode, $cm->sectionnumber, get_fast_modinfo($course));
+                        list($sectionarray, $activityarray) = $this->generate_sections_and_activities($course);
+                        $activities = $this->load_section_activities($sections[$cm->sectionnumber]->sectionnode, $cm->sectionnumber, $activityarray);
                     } else {
                         $activities = array();
                         if ($activity = $this->load_stealth_activity($coursenode, get_fast_modinfo($course))) {
@@ -1209,6 +1217,9 @@ class global_navigation extends navigation_node {
             case CONTEXT_USER :
                 $course = $this->page->course;
                 if ($course->id != SITEID) {
+                    if ($showcategories && !$issite && !$ismycourse) {
+                        $this->load_all_categories($course->category, $showcategories);
+                    }
                     // Load the course associated with the user into the navigation
                     $coursenode = $this->load_course($course);
                     // If the user is not enrolled then we only want to show the
@@ -1322,6 +1333,7 @@ class global_navigation extends navigation_node {
     /**
      * Loads of the the courses in Moodle into the navigation.
      *
+     * @global moodle_database $DB
      * @param string|array $categoryids Either a string or array of category ids to load courses for
      * @return array An array of navigation_node
      */
@@ -1330,30 +1342,38 @@ class global_navigation extends navigation_node {
 
         if ($categoryids !== null) {
             if (is_array($categoryids)) {
-                list ($select, $params) = $DB->get_in_or_equal($categoryids);
+                list ($categoryselect, $params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'catid');
             } else {
-                $select = '= ?';
-                $params = array($categoryids);
+                $categoryselect = '= :categoryid';
+                $params = array('categoryid', $categoryids);
             }
-            array_unshift($params, SITEID);
-            $select = ' AND c.category '.$select;
+            $params['siteid'] = SITEID;
+            $categoryselect = ' AND c.category '.$categoryselect;
         } else {
-            $params = array(SITEID);
-            $select = '';
+            $params = array('siteid' => SITEID);
+            $categoryselect = '';
+        }
+
+        if (count($this->addedcategories) > 0) {
+            list($courseselect, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses), SQL_PARAMS_NAMED, 'course', false);
+            $params += $courseparams;
+        } else {
+            $courseselect = '';
         }
 
         list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
-        $sql = "SELECT c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.category,cat.path AS categorypath $ccselect
-                FROM {course} c
-                $ccjoin
-                LEFT JOIN {course_categories} cat ON cat.id=c.category
-                WHERE c.id <> ?$select
-                ORDER BY c.sortorder ASC";
+        list($courseids, $courseparams) = $DB->get_in_or_equal(array_keys($this->addedcourses) + array(SITEID), SQL_PARAMS_NAMED, 'lcourse', false);
+        $sql = "SELECT c.id, c.sortorder, c.visible, c.fullname, c.shortname, c.category, cat.path AS categorypath $ccselect
+                  FROM {course} c
+                       $ccjoin
+             LEFT JOIN {course_categories} cat ON cat.id=c.category
+                 WHERE c.id {$courseids} {$categoryselect}
+              ORDER BY c.sortorder ASC";
         $limit = 20;
         if (!empty($CFG->navcourselimit)) {
             $limit = $CFG->navcourselimit;
         }
-        $courses = $DB->get_records_sql($sql, $params, 0, $limit);
+        $courses = $DB->get_records_sql($sql, $params + $courseparams, 0, $limit);
 
         $coursenodes = array();
         foreach ($courses as $course) {
@@ -1366,87 +1386,120 @@ class global_navigation extends navigation_node {
     /**
      * Loads all categories (top level or if an id is specified for that category)
      *
-     * @param int $categoryid
+     * @param int $categoryid The category id to load or null/0 to load all base level categories
+     * @param bool $showbasecategories If set to true all base level categories will be loaded as well
+     *        as the requested category and any parent categories.
      * @return void
      */
-    protected function load_all_categories($categoryid=null) {
+    protected function load_all_categories($categoryid = null, $showbasecategories = false) {
         global $DB;
-        if ($categoryid == null)  {
+
+        // Check if this category has already been loaded
+        if ($categoryid !== null && array_key_exists($categoryid, $this->addedcategories) && $this->addedcategories[$categoryid]->children->count() > 0) {
+            return $this->addedcategories[$categoryid];
+        }
+
+        $coursestoload = array();
+        if (empty($categoryid)) { // can be 0
+            // We are going to load all of the first level categories (categories without parents)
             $categories = $DB->get_records('course_categories', array('parent'=>'0'), 'sortorder');
+        } else if (array_key_exists($categoryid, $this->addedcategories)) {
+            // The category itself has been loaded already so we just need to ensure its subcategories
+            // have been loaded
+            list($sql, $params) = $DB->get_in_or_equal(array_keys($this->addedcategories), SQL_PARAMS_NAMED, 'parent', false);
+            if ($showbasecategories) {
+                // We need to include categories with parent = 0 as well
+                $sql = "SELECT *
+                          FROM {course_categories} cc
+                         WHERE (parent = :categoryid OR parent = 0) AND
+                               parent {$sql}
+                      ORDER BY sortorder";
+            } else {
+                $sql = "SELECT *
+                          FROM {course_categories} cc
+                         WHERE parent = :categoryid AND
+                               parent {$sql}
+                      ORDER BY sortorder";
+            }
+            $params['categoryid'] = $categoryid;
+            $categories = $DB->get_records_sql($sql, $params);
+            if (count($categories) == 0) {
+                // There are no further categories that require loading.
+                return;
+            }
         } else {
-            $category = $DB->get_record('course_categories', array('id'=>$categoryid), '*', MUST_EXIST);
-            $wantedids = explode('/', trim($category->path, '/'));
-            list($select, $params) = $DB->get_in_or_equal($wantedids);
+            // This category hasn't been loaded yet so we need to fetch it, work out its category path
+            // and load this category plus all its parents and subcategories
+            $category = $DB->get_record('course_categories', array('id'=>$categoryid), 'path', MUST_EXIST);
+            $coursestoload = explode('/', trim($category->path, '/'));
+            list($select, $params) = $DB->get_in_or_equal($coursestoload);
             $select = 'id '.$select.' OR parent '.$select;
+            if ($showbasecategories) {
+                $select .= ' OR parent = 0';
+            }
             $params = array_merge($params, $params);
             $categories = $DB->get_records_select('course_categories', $select, $params, 'sortorder');
         }
-        $structured = array();
-        $categoriestoload = array();
-        foreach ($categories as $category) {
-            if ($category->parent == '0') {
-                $structured[$category->id] = array('category'=>$category, 'children'=>array());
+
+        // Now we have an array of categories we need to add them to the navigation.
+        while (!empty($categories)) {
+            $category = reset($categories);
+            if (array_key_exists($category->id, $this->addedcategories)) {
+                // Do nothing
+            } else if ($category->parent == '0') {
+                $this->add_category($category, $this->rootnodes['courses']);
+            } else if (array_key_exists($category->parent, $this->addedcategories)) {
+                $this->add_category($category, $this->addedcategories[$category->parent]);
             } else {
-                if ($category->parent == $categoryid) {
-                    $categoriestoload[] = $category->id;
-                }
-                $parents = array();
-                $id = $category->parent;
-                while ($id != '0') {
-                    $parents[] = $id;
-                    if (!array_key_exists($id, $categories)) {
-                        $categories[$id] = $DB->get_record('course_categories', array('id'=>$id), '*', MUST_EXIST);
-                    }
-                    $id = $categories[$id]->parent;
-                }
-                $parents = array_reverse($parents);
-                $parentref = &$structured[array_shift($parents)];
-                foreach ($parents as $parent) {
-                    if (!array_key_exists($parent, $parentref['children'])) {
-                        $parentref['children'][$parent] = array('category'=>$categories[$parent], 'children'=>array());
+                // This category isn't in the navigation and niether is it's parent (yet).
+                // We need to go through the category path and add all of its components in order.
+                $path = explode('/', trim($category->path, '/'));
+                foreach ($path as $catid) {
+                    if (!array_key_exists($catid, $this->addedcategories)) {
+                        // This category isn't in the navigation yet so add it.
+                        $subcategory = $categories[$catid];
+                        if (array_key_exists($subcategory->parent, $this->addedcategories)) {
+                            // The parent is in the category (as we'd expect) so add it now.
+                            $this->add_category($subcategory, $this->addedcategories[$subcategory->parent]);
+                            // Remove the category from the categories array.
+                            unset($categories[$catid]);
+                        } else {
+                            // We should never ever arrive here - if we have then there is a bigger
+                            // problem at hand.
+                            throw coding_exception('Category path order is incorrect and/or there are missing categories');
+                        }
                     }
-                    $parentref = &$parentref['children'][$parent];
-                }
-                if (!array_key_exists($category->id, $parentref['children'])) {
-                    $parentref['children'][$category->id] = array('category'=>$category, 'children'=>array());
                 }
             }
+            // Remove the category from the categories array now that we know it has been added.
+            unset($categories[$category->id]);
         }
-
-        foreach ($structured as $category) {
-            $this->add_category($category, $this->rootnodes['courses']);
-        }
-
-        if ($categoryid !== null && count($wantedids)) {
-            foreach ($wantedids as $catid) {
-                $this->load_all_courses($catid);
-            }
+        // Check if there are any categories to load.
+        if (count($coursestoload) > 0) {
+            $this->load_all_courses($coursestoload);
         }
     }
 
     /**
      * Adds a structured category to the navigation in the correct order/place
      *
-     * @param object $cat
+     * @param stdClass $category
      * @param navigation_node $parent
      */
-    protected function add_category($cat, navigation_node $parent) {
-        $categorynode = $parent->get($cat['category']->id, navigation_node::TYPE_CATEGORY);
-        if (!$categorynode) {
-            $category = $cat['category'];
-            $url = new moodle_url('/course/category.php', array('id'=>$category->id));
-            $categorynode = $parent->add($category->name, $url, self::TYPE_CATEGORY, $category->name, $category->id);
-            if (empty($category->visible)) {
-                if (has_capability('moodle/category:viewhiddencategories', get_system_context())) {
-                    $categorynode->hidden = true;
-                } else {
-                    $categorynode->display = false;
-                }
+    protected function add_category(stdClass $category, navigation_node $parent) {
+        if (array_key_exists($category->id, $this->addedcategories)) {
+            continue;
+        }
+        $url = new moodle_url('/course/category.php', array('id' => $category->id));
+        $categorynode = $parent->add($category->name, $url, self::TYPE_CATEGORY, $category->name, $category->id);
+        if (empty($category->visible)) {
+            if (has_capability('moodle/category:viewhiddencategories', get_system_context())) {
+                $categorynode->hidden = true;
+            } else {
+                $categorynode->display = false;
             }
         }
-        foreach ($cat['children'] as $child) {
-            $this->add_category($child, $categorynode);
-        }
+        $this->addedcategories[$category->id] = &$categorynode;
     }
 
     /**
@@ -1457,16 +1510,12 @@ class global_navigation extends navigation_node {
      */
     protected function load_course(stdClass $course) {
         if ($course->id == SITEID) {
-            $coursenode = $this->rootnodes['site'];
-        } else if (array_key_exists($course->id, $this->mycourses)) {
-            if (!isset($this->mycourses[$course->id]->coursenode)) {
-                $this->mycourses[$course->id]->coursenode = $this->add_course($course);
-            }
-            $coursenode = $this->mycourses[$course->id]->coursenode;
+            return $this->rootnodes['site'];
+        } else if (array_key_exists($course->id, $this->addedcourses)) {
+            return $this->addedcourses[$course->id];
         } else {
-            $coursenode = $this->add_course($course);
+            return $this->add_course($course);
         }
-        return $coursenode;
     }
 
     /**
@@ -1501,50 +1550,93 @@ class global_navigation extends navigation_node {
         }
     }
 
+    /**
+     * Generates an array of sections and an array of activities for the given course.
+     *
+     * This method uses the cache to improve performance and avoid the get_fast_modinfo call
+     *
+     * @param stdClass $course
+     * @return array Array($sections, $activities)
+     */
+    protected function generate_sections_and_activities(stdClass $course) {
+        global $CFG;
+        require_once($CFG->dirroot.'/course/lib.php');
+
+        if (!$this->cache->cached('course_sections_'.$course->id) || !$this->cache->cached('course_activites_'.$course->id)) {
+            $modinfo = get_fast_modinfo($course);
+            $sections = array_slice(get_all_sections($course->id), 0, $course->numsections+1, true);
+
+            $activities = array();
+
+            foreach ($sections as $key => $section) {
+                $sections[$key]->hasactivites = false;
+                if (!array_key_exists($section->section, $modinfo->sections)) {
+                    continue;
+                }
+                foreach ($modinfo->sections[$section->section] as $cmid) {
+                    $cm = $modinfo->cms[$cmid];
+                    if (!$cm->uservisible) {
+                        continue;
+                    }
+                    $activity = new stdClass;
+                    $activity->section = $section->section;
+                    $activity->name = $cm->name;
+                    $activity->icon = $cm->icon;
+                    $activity->iconcomponent = $cm->iconcomponent;
+                    $activity->id = $cm->id;
+                    $activity->hidden = (!$cm->visible);
+                    $activity->modname = $cm->modname;
+                    $activity->nodetype = navigation_node::NODETYPE_LEAF;
+                    $url = $cm->get_url();
+                    if (!$url) {
+                        $activity->url = null;
+                        $activity->display = false;
+                    } else {
+                        $activity->url = $cm->get_url()->out();
+                        $activity->display = true;
+                        if (self::module_extends_navigation($cm->modname)) {
+                            $activity->nodetype = navigation_node::NODETYPE_BRANCH;
+                        }
+                    }
+                    $activities[$cmid] = $activity;
+                    $sections[$key]->hasactivites = true;
+                }
+            }
+            $this->cache->set('course_sections_'.$course->id, $sections);
+            $this->cache->set('course_activites_'.$course->id, $activities);
+        } else {
+            $sections = $this->cache->{'course_sections_'.$course->id};
+            $activities = $this->cache->{'course_activites_'.$course->id};
+        }
+        return array($sections, $activities);
+    }
+
     /**
      * Generically loads the course sections into the course's navigation.
      *
      * @param stdClass $course
      * @param navigation_node $coursenode
-     * @param string $name The string that identifies each section. e.g Topic, or Week
-     * @param string $activeparam The url used to identify the active section
+     * @param string $courseformat The course format
      * @return array An array of course section nodes
      */
     public function load_generic_course_sections(stdClass $course, navigation_node $coursenode, $courseformat='unknown') {
         global $CFG, $DB, $USER;
-
         require_once($CFG->dirroot.'/course/lib.php');
 
-        if (!$this->cache->cached('modinfo'.$course->id)) {
-            $this->cache->set('modinfo'.$course->id, get_fast_modinfo($course));
-        }
-        $modinfo = $this->cache->{'modinfo'.$course->id};
-
-        if (!$this->cache->cached('coursesections'.$course->id)) {
-            $this->cache->set('coursesections'.$course->id, array_slice(get_all_sections($course->id), 0, $course->numsections+1, true));
-        }
-        $sections = $this->cache->{'coursesections'.$course->id};
-
-        $viewhiddensections = has_capability('moodle/course:viewhiddensections', $this->page->context);
-
-        $activesection = course_get_display($course->id);
+        list($sections, $activities) = $this->generate_sections_and_activities($course);
 
         $namingfunction = 'callback_'.$courseformat.'_get_section_name';
         $namingfunctionexists = (function_exists($namingfunction));
+        $activesection = course_get_display($course->id);
+        $viewhiddensections = has_capability('moodle/course:viewhiddensections', $this->page->context);
 
-        $activeparamfunction = 'callback_'.$courseformat.'_request_key';
-        if (function_exists($activeparamfunction)) {
-            $activeparam = $activeparamfunction();
-        } else {
-            $activeparam = 'section';
-        }
         $navigationsections = array();
-        foreach ($sections as $sectionid=>$section) {
+        foreach ($sections as $sectionid => $section) {
             $section = clone($section);
             if ($course->id == SITEID) {
-                $this->load_section_activities($coursenode, $section->section, $modinfo);
+                $this->load_section_activities($coursenode, $section->section, $activities);
             } else {
-                if ((!$viewhiddensections && !$section->visible) || (!$this->showemptysections && !array_key_exists($section->section, $modinfo->sections))) {
+                if ((!$viewhiddensections && !$section->visible) || (!$this->showemptysections && !$section->hasactivites)) {
                     continue;
                 }
                 if ($namingfunctionexists) {
@@ -1557,9 +1649,9 @@ class global_navigation extends navigation_node {
                 $sectionnode = $coursenode->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);
                 $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
                 $sectionnode->hidden = (!$section->visible);
-                if ($this->page->context->contextlevel != CONTEXT_MODULE && ($sectionnode->isactive || ($activesection && $section->section == $activesection))) {
+                if ($this->page->context->contextlevel != CONTEXT_MODULE && $section->hasactivites && ($sectionnode->isactive || ($activesection && $section->section == $activesection))) {
                     $sectionnode->force_open();
-                    $this->load_section_activities($sectionnode, $section->section, $modinfo);
+                    $this->load_section_activities($sectionnode, $section->section, $activities);
                 }
                 $section->sectionnode = $sectionnode;
                 $navigationsections[$sectionid] = $section;
@@ -1570,42 +1662,40 @@ class global_navigation extends navigation_node {
     /**
      * Loads all of the activities for a section into the navigation structure.
      *
+     * @todo 2.2 - $activities should always be an array and we should no longer check for it being a
+     *             course_modinfo object
+     *
      * @param navigation_node $sectionnode
      * @param int $sectionnumber
      * @param course_modinfo $modinfo Object returned from {@see get_fast_modinfo()}
      * @return array Array of activity nodes
      */
-    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, course_modinfo $modinfo) {
-        if (!array_key_exists($sectionnumber, $modinfo->sections)) {
-            return true;
-        }
+    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $activities) {
 
-        $activities = array();
+        if ($activities instanceof course_modinfo) {
+            debugging('global_navigation::load_section_activities argument 3 should now recieve an array of activites. See that method for an example.', DEBUG_DEVELOPER);
+            list($sections, $activities) = $this->generate_sections_and_activities($activities->course);
+        }
 
-        foreach ($modinfo->sections[$sectionnumber] as $cmid) {
-            $cm = $modinfo->cms[$cmid];
-            if (!$cm->uservisible) {
+        $activitynodes = array();
+        foreach ($activities as $activity) {
+            if ($activity->section != $sectionnumber) {
                 continue;
             }
-            if ($cm->icon) {
-                $icon = new pix_icon($cm->icon, get_string('modulename', $cm->modname), $cm->iconcomponent);
+            if ($activity->icon) {
+                $icon = new pix_icon($activity->icon, get_string('modulename', $activity->modname), $activity->iconcomponent);
             } else {
-                $icon = new pix_icon('icon', get_string('modulename', $cm->modname), $cm->modname);
+                $icon = new pix_icon('icon', get_string('modulename', $activity->modname), $activity->modname);
             }
-            $url = $cm->get_url();
-            $activitynode = $sectionnode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
-            $activitynode->title(get_string('modulename', $cm->modname));
-            $activitynode->hidden = (!$cm->visible);
-            if (!$url) {
-                // Do not show activities that don't have links!
-                $activitynode->display = false;
-            } else if ($this->module_extends_navigation($cm->modname)) {
-                $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
-            }
-            $activities[$cmid] = $activitynode;
+            $activitynode = $sectionnode->add(format_string($activity->name), $activity->url, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
+            $activitynode->title(get_string('modulename', $activity->modname));
+            $activitynode->hidden = $activity->hidden;
+            $activitynode->display = $activity->display;
+            $activitynode->nodetype = $activity->nodetype;
+            $activitynodes[$activity->id] = $activitynode;
         }
 
-        return $activities;
+        return $activitynodes;
     }
     /**
      * Loads a stealth module from unavailable section
@@ -1633,7 +1723,7 @@ class global_navigation extends navigation_node {
         if (!$url) {
             // Don't show activities that don't have links!
             $activitynode->display = false;
-        } else if ($this->module_extends_navigation($cm->modname)) {
+        } else if (self::module_extends_navigation($cm->modname)) {
             $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
         }
         return $activitynode;
@@ -1698,7 +1788,10 @@ class global_navigation extends navigation_node {
             $user = $USER;
         } else if (!is_object($user)) {
             // If the user is not an object then get them from the database
-            $user = $DB->get_record('user', array('id'=>(int)$user), '*', MUST_EXIST);
+            list($select, $join) = context_instance_preload_sql('u.id', CONTEXT_USER, 'ctx');
+            $sql = "SELECT u.* $select FROM {user} u $join WHERE u.id = :userid";
+            $user = $DB->get_record_sql($sql, array('userid' => (int)$user), MUST_EXIST);
+            context_instance_preload($user);
         }
 
         $iscurrentuser = ($user->id == $USER->id);
@@ -1709,14 +1802,7 @@ class global_navigation extends navigation_node {
         $course = $this->page->course;
         $baseargs = array('id'=>$user->id);
         if ($course->id != SITEID && (!$iscurrentuser || $forceforcontext)) {
-            if (array_key_exists($course->id, $this->mycourses)) {
-                $coursenode = $this->mycourses[$course->id]->coursenode;
-            } else {
-                $coursenode = $this->rootnodes['courses']->find($course->id, navigation_node::TYPE_COURSE);
-                if (!$coursenode) {
-                    $coursenode = $this->load_course($course);
-                }
-            }
+            $coursenode = $this->load_course($course);
             $baseargs['course'] = $course->id;
             $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
             $issitecourse = false;
@@ -1776,9 +1862,15 @@ class global_navigation extends navigation_node {
 
         // Add blog nodes
         if (!empty($CFG->bloglevel)) {
-            require_once($CFG->dirroot.'/blog/lib.php');
-            // Get all options for the user
-            $options = blog_get_options_for_user($user);
+            if (!$this->cache->cached('userblogoptions'.$user->id)) {
+                require_once($CFG->dirroot.'/blog/lib.php');
+                // Get all options for the user
+                $options = blog_get_options_for_user($user);
+                $this->cache->set('userblogoptions'.$user->id, $options);
+            } else {
+                $options = $this->cache->{'userblogoptions'.$user->id};
+            }
+
             if (count($options) > 0) {
                 $blogs = $usernode->add(get_string('blogs', 'blog'), null, navigation_node::TYPE_CONTAINER);
                 foreach ($options as $option) {
@@ -1879,9 +1971,16 @@ class global_navigation extends navigation_node {
         // If the user is the current user add the repositories for the current user
         $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
         if ($iscurrentuser) {
-            require_once($CFG->dirroot . '/repository/lib.php');
-            $editabletypes = repository::get_editable_types($usercontext);
-            if (!empty($editabletypes)) {
+            if (!$this->cache->cached('contexthasrepos'.$usercontext->id)) {
+                require_once($CFG->dirroot . '/repository/lib.php');
+                $editabletypes = repository::get_editable_types($usercontext);
+                $haseditabletypes = !empty($editabletypes);
+                unset($editabletypes);
+                $this->cache->set('contexthasrepos'.$usercontext->id, $haseditabletypes);
+            } else {
+                $haseditabletypes = $this->cache->{'contexthasrepos'.$usercontext->id};
+            }
+            if ($haseditabletypes) {
                 $usernode->add(get_string('repositories', 'repository'), new moodle_url('/repository/manage_instances.php', array('contextid' => $usercontext->id)));
             }
         } else if ($course->id == SITEID && has_capability('moodle/user:viewdetails', $usercontext) && (!in_array('mycourses', $hiddenfields) || has_capability('moodle/user:viewhiddendetails', $coursecontext))) {
@@ -1955,28 +2054,24 @@ class global_navigation extends navigation_node {
     /**
      * This method simply checks to see if a given module can extend the navigation.
      *
+     * TODO: A shared caching solution should be used to save details on what extends navigation
+     *
      * @param string $modname
      * @return bool
      */
-    protected function module_extends_navigation($modname) {
+    protected static function module_extends_navigation($modname) {
         global $CFG;
-        if ($this->cache->cached($modname.'_extends_navigation')) {
-            return $this->cache->{$modname.'_extends_navigation'};
-        }
-        $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php';
-        $function = $modname.'_extend_navigation';
-        if (function_exists($function)) {
-            $this->cache->{$modname.'_extends_navigation'} = true;
-            return true;
-        } else if (file_exists($file)) {
-            require_once($file);
-            if (function_exists($function)) {
-                $this->cache->{$modname.'_extends_navigation'} = true;
-                return true;
+        static $extendingmodules = array();
+        if (!array_key_exists($modname, $extendingmodules)) {
+            $extendingmodules[$modname] = false;
+            $file = $CFG->dirroot.'/mod/'.$modname.'/lib.php';
+            if (file_exists($file)) {
+                $function = $modname.'_extend_navigation';
+                require_once($file);
+                $extendingmodules[$modname] = (function_exists($function));
             }
         }
-        $this->cache->{$modname.'_extends_navigation'} = false;
-        return false;
+        return $extendingmodules[$modname];
     }
     /**
      * Extends the navigation for the given user.
@@ -2001,22 +2096,24 @@ class global_navigation extends navigation_node {
      * @param stdClass $course
      * @return navigation_node
      */
-    public function add_course(stdClass $course, $forcegeneric = false) {
+    public function add_course(stdClass $course, $forcegeneric = false, $ismycourse = false) {
         global $CFG;
 
-        if ($course->id != SITEID) {
-            if (!$course->visible) {
-                if (is_role_switched($course->id)) {
-                    // user has to be able to access course in order to switch, let's skip the visibility test here
-                } else if (!has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
-                    return false;
-                }
+        // We found the course... we can return it now :)
+        if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) {
+            return $this->addedcourses[$course->id];
+        }
+
+        if ($course->id != SITEID && !$course->visible) {
+            if (is_role_switched($course->id)) {
+                // user has to be able to access course in order to switch, let's skip the visibility test here
+            } else if (!has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                return false;
             }
         }
 
         $issite = ($course->id == SITEID);
-        $ismycourse = (array_key_exists($course->id, $this->mycourses) && !$forcegeneric);
-        $displaycategories = (!$ismycourse && !$issite && !empty($CFG->navshowcategories));
+        $ismycourse = ($ismycourse && !$forcegeneric);
         $shortname = $course->shortname;
 
         if ($issite) {
@@ -2031,52 +2128,23 @@ class global_navigation extends navigation_node {
             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
         }
 
-        if ($displaycategories) {
+        if (!$ismycourse && !$issite && !empty($CFG->navshowcategories) && !empty($course->category)) {
             // We need to load the category structure for this course
-            $categoryfound = false;
-            if (!empty($course->categorypath)) {
-                $categories = explode('/', trim($course->categorypath, '/'));
-                $category = $parent;
-                while ($category && $categoryid = array_shift($categories)) {
-                    $category = $category->get($categoryid, self::TYPE_CATEGORY);
-                }
-                if ($category instanceof navigation_node) {
-                    $parent = $category;
-                    $categoryfound = true;
-                }
-                if (!$categoryfound && $forcegeneric) {
-                    $this->load_all_categories($course->category);
-                    if ($category = $parent->find($course->category, self::TYPE_CATEGORY)) {
-                        $parent = $category;
-                        $categoryfound = true;
-                    }
-                }
-            } else if (!empty($course->category)) {
-                $this->load_all_categories($course->category);
-                if ($category = $parent->find($course->category, self::TYPE_CATEGORY)) {
-                    $parent = $category;
-                    $categoryfound = true;
-                }
-                if (!$categoryfound && !$forcegeneric) {
-                    $this->load_all_categories($course->category);
-                    if ($category = $parent->find($course->category, self::TYPE_CATEGORY)) {
-                        $parent = $category;
-                        $categoryfound = true;
-                    }
-                }
+            $this->load_all_categories($course->category);
+            $parent = $this->addedcategories[$course->category];
+            // This could lead to the course being created so we should check whether it is the case again
+            if (!$forcegeneric && array_key_exists($course->id, $this->addedcourses)) {
+                return $this->addedcourses[$course->id];
             }
         }
 
-        // We found the course... we can return it now :)
-        if ($coursenode = $parent->get($course->id, self::TYPE_COURSE)) {
-            return $coursenode;
-        }
-
         $coursenode = $parent->add($shortname, $url, self::TYPE_COURSE, $shortname, $course->id);
         $coursenode->nodetype = self::NODETYPE_BRANCH;
         $coursenode->hidden = (!$course->visible);
         $coursenode->title($course->fullname);
-        $this->addedcourses[$course->id] = &$coursenode;
+        if (!$forcegeneric) {
+            $this->addedcourses[$course->id] = &$coursenode;
+        }
         if ($ismycourse && !empty($CFG->navshowallcourses)) {
             // We need to add this course to the general courses node as well as the
             // my courses node, rerun the function with the kill param
@@ -2093,6 +2161,7 @@ class global_navigation extends navigation_node {
                 }
             }
         }
+
         return $coursenode;
     }
     /**
@@ -2356,7 +2425,7 @@ class global_navigation_for_ajax extends global_navigation {
         $this->initialised = true;
 
         $this->rootnodes = array();
-        $this->rootnodes['site']      = $this->add_course($SITE);
+        $this->rootnodes['site']    = $this->add_course($SITE);
         $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
 
         // Branchtype will be one of navigation_node::TYPE_*
@@ -2393,7 +2462,8 @@ class global_navigation_for_ajax extends global_navigation {
                 $coursenode = $this->add_course($course);
                 $this->add_course_essentials($coursenode, $course);
                 $sections = $this->load_course_sections($course, $coursenode);
-                $this->load_section_activities($sections[$course->sectionnumber]->sectionnode, $course->sectionnumber, get_fast_modinfo($course));
+                list($sectionarray, $activities) = $this->generate_sections_and_activities($course);
+                $this->load_section_activities($sections[$course->sectionnumber]->sectionnode, $course->sectionnumber, $activities);
                 break;
             case self::TYPE_ACTIVITY :
                 $sql = "SELECT c.*
@@ -2411,7 +2481,8 @@ class global_navigation_for_ajax extends global_navigation {
                     $modulenode = $this->load_activity($cm, $course, $coursenode->find($cm->id, self::TYPE_ACTIVITY));
                 } else {
                     $sections   = $this->load_course_sections($course, $coursenode);
-                    $activities = $this->load_section_activities($sections[$cm->sectionnum]->sectionnode, $cm->sectionnum, get_fast_modinfo($course));
+                    list($sectionarray, $activities) = $this->generate_sections_and_activities($course);
+                    $activities = $this->load_section_activities($sections[$cm->sectionnum]->sectionnode, $cm->sectionnum, $activities);
                     $modulenode = $this->load_activity($cm, $course, $activities[$cm->id]);
                 }
                 break;
@@ -2664,8 +2735,6 @@ class navbar extends navigation_node {
 class settings_navigation extends navigation_node {
     /** @var stdClass */
     protected $context;
-    /** @var navigation_cache */
-    protected $cache;
     /** @var moodle_page */
     protected $page;
     /** @var string */
@@ -2674,6 +2743,8 @@ class settings_navigation extends navigation_node {
     protected $initialised = false;
     /** @var array */
     protected $userstoextendfor = array();
+    /** @var navigation_cache **/
+    protected $cache;
 
     /**
      * Sets up the object with basic settings and preparse it for use
@@ -2932,17 +3003,6 @@ class settings_navigation extends navigation_node {
     /**
      * Generate the list of modules for the given course.
      *
-     * The array of resources and activities that can be added to a course is then
-     * stored in the cache so that we can access it for anywhere.
-     * It saves us generating it all the time
-     *
-     * <code php>
-     * // To get resources:
-     * $this->cache->{'course'.$courseid.'resources'}
-     * // To get activities:
-     * $this->cache->{'course'.$courseid.'activities'}
-     * </code>
-     *
      * @param stdClass $course The course to get modules for
      */
     protected function get_course_modules($course) {
@@ -2987,8 +3047,7 @@ class settings_navigation extends navigation_node {
                 }
             }
         }
-        $this->cache->{'course'.$course->id.'resources'} = $resources;
-        $this->cache->{'course'.$course->id.'activities'} = $activities;
+        return array($resources, $activities);
     }
 
     /**
@@ -2998,7 +3057,7 @@ class settings_navigation extends navigation_node {
      * @return navigation_node|false
      */
     protected function load_course_settings($forceopen = false) {
-        global $CFG, $USER, $SESSION, $OUTPUT;
+        global $CFG;
 
         $course = $this->page->course;
         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
@@ -3109,12 +3168,21 @@ class settings_navigation extends navigation_node {
         require_once($CFG->dirroot.'/question/editlib.php');
         question_extend_settings_navigation($coursenode, $coursecontext)->trim_if_empty();
 
-        // Repository Instances
-        require_once($CFG->dirroot.'/repository/lib.php');
-        $editabletypes = repository::get_editable_types($coursecontext);
-        if (has_capability('moodle/course:update', $coursecontext) && !empty($editabletypes)) {
-            $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$coursecontext->id));
-            $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
+        if (has_capability('moodle/course:update', $coursecontext)) {
+            // Repository Instances
+            if (!$this->cache->cached('contexthasrepos'.$coursecontext->id)) {
+                require_once($CFG->dirroot . '/repository/lib.php');
+                $editabletypes = repository::get_editable_types($coursecontext);
+                $haseditabletypes = !empty($editabletypes);
+                unset($editabletypes);
+                $this->cache->set('contexthasrepos'.$coursecontext->id, $haseditabletypes);
+            } else {
+                $haseditabletypes = $this->cache->{'contexthasrepos'.$coursecontext->id};
+            }
+            if ($haseditabletypes) {
+                $url = new moodle_url('/repository/manage_instances.php', array('contextid' => $coursecontext->id));
+                $coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
+            }
         }
 
         // Manage files
@@ -3127,7 +3195,7 @@ class settings_navigation extends navigation_node {
         // Switch roles
         $roles = array();
         $assumedrole = $this->in_alternative_role();
-        if ($assumedrole!==false) {
+        if ($assumedrole !== false) {
             $roles[0] = get_string('switchrolereturn');
         }
         if (has_capability('moodle/role:switchroles', $coursecontext)) {
@@ -3148,9 +3216,8 @@ class settings_navigation extends navigation_node {
             }
             $returnurl = $this->page->url;
             $returnurl->param('sesskey', sesskey());
-            $SESSION->returnurl = serialize($returnurl);
-            foreach ($roles as $key=>$name) {
-                $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>'1'));
+            foreach ($roles as $key => $name) {
+                $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>$returnurl->out(false)));
                 $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
             }
         }
@@ -3189,11 +3256,7 @@ class settings_navigation extends navigation_node {
             $addactivity->force_open();
         }
 
-        if (!$this->cache->cached('course'.$course->id.'resources')) {
-            $this->get_course_modules($course);
-        }
-        $resources = $this->cache->{'course'.$course->id.'resources'};
-        $activities = $this->cache->{'course'.$course->id.'activities'};
+        $this->get_course_modules($course);
 
         $textlib = textlib_get_instance();
 
@@ -3416,6 +3479,7 @@ class settings_navigation extends navigation_node {
      * This function gets called by {@link load_user_settings()} and actually works out
      * what can be shown/done
      *
+     * @global moodle_database $DB
      * @param int $courseid The current course' id
      * @param int $userid The user id to load for
      * @param string $gstitle The string to pass to get_string for the branch title
@@ -3428,7 +3492,10 @@ class settings_navigation extends navigation_node {
             if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
                 $course = $this->page->course;
             } else {
-                $course = $DB->get_record("course", array("id"=>$courseid), '*', MUST_EXIST);
+                list($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+                $sql = "SELECT c.* $select FROM {course} c $join WHERE c.id = :courseid";
+                $course = $DB->get_record_sql($sql, array('courseid' => $courseid), MUST_EXIST);
+                context_instance_preload($course);
             }
         } else {
             $course = $SITE;
@@ -3442,21 +3509,31 @@ class settings_navigation extends navigation_node {
             $user = $USER;
             $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
         } else {
-            if (!$user = $DB->get_record('user', array('id'=>$userid))) {
+
+            list($select, $join) = context_instance_preload_sql('u.id', CONTEXT_USER, 'ctx');
+            $sql = "SELECT u.* $select FROM {user} u $join WHERE u.id = :userid";
+            $user = $DB->get_record_sql($sql, array('userid' => $userid), IGNORE_MISSING);
+            if (!$user) {
                 return false;
             }
+            context_instance_preload($user);
+
             // Check that the user can view the profile
-            $usercontext = get_context_instance(CONTEXT_USER, $user->id);       // User context
-            if ($course->id==SITEID) {
-                if ($CFG->forceloginforprofiles && !has_coursecontact_role($user->id) && !has_capability('moodle/user:viewdetails', $usercontext)) {  // Reduce possibility of "browsing" userbase at site level
+            $usercontext = get_context_instance(CONTEXT_USER, $user->id); // User context
+            $canviewuser = has_capability('moodle/user:viewdetails', $usercontext);
+
+            if ($course->id == SITEID) {
+                if ($CFG->forceloginforprofiles && !has_coursecontact_role($user->id) && !$canviewuser) {  // Reduce possibility of "browsing" userbase at site level
                     // Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
                     return false;
                 }
             } else {
-                if ((!has_capability('moodle/user:viewdetails', $coursecontext) && !has_capability('moodle/user:viewdetails', $usercontext)) || !can_access_course($coursecontext, $user->id)) {
+                $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext);
+                $canaccessallgroups = has_capability('moodle/site:accessallgroups', $coursecontext);
+                if ((!$canviewusercourse && !$canviewuser) || !can_access_course($coursecontext, $user->id)) {
                     return false;
                 }
-                if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
+                if (!$canaccessallgroups && groups_get_course_groupmode($course) == SEPARATEGROUPS) {
                     // If groups are in use, make sure we can see that group
                     return false;
                 }
@@ -3495,35 +3572,34 @@ class settings_navigation extends navigation_node {
             return true;
         }
 
+        $userauthplugin = false;
+        if (!empty($user->auth)) {
+            $userauthplugin = get_auth_plugin($user->auth);
+        }
+
         // Add the profile edit link
         if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
             if (($currentuser || is_siteadmin($USER) || !is_siteadmin($user)) && has_capability('moodle/user:update', $systemcontext)) {
                 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
                 $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
             } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_siteadmin($user)) || ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
-                if (!empty($user->auth)) {
-                    $userauth = get_auth_plugin($user->auth);
-                    if ($userauth->can_edit_profile()) {
-                        $url = $userauth->edit_profile_url();
-                        if (empty($url)) {
-                            $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
-                        }
-                        $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
+                if ($userauthplugin && $userauthplugin->can_edit_profile()) {
+                    $url = $userauthplugin->edit_profile_url();
+                    if (empty($url)) {
+                        $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
                     }
+                    $usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
                 }
             }
         }
 
         // Change password link
-        if (!empty($user->auth)) {
-            $userauth = get_auth_plugin($user->auth);
-            if ($currentuser && !session_is_loggedinas() && $userauth->can_change_password() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext)) {
-                $passwordchangeurl = $userauth->change_password_url();
-                if (empty($passwordchangeurl)) {
-                    $passwordchangeurl = new moodle_url('/login/change_password.php', array('id'=>$course->id));
-                }
-                $usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
+        if ($userauthplugin && $currentuser && !session_is_loggedinas() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext) && $userauthplugin->can_change_password()) {
+            $passwordchangeurl = $userauthplugin->change_password_url();
+            if (empty($passwordchangeurl)) {
+                $passwordchangeurl = new moodle_url('/login/change_password.php', array('id'=>$course->id));
             }
+            $usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
         }
 
         // View the roles settings
@@ -3578,10 +3654,17 @@ class settings_navigation extends navigation_node {
         }
 
         // Repository
-        if (!$currentuser) {
-            require_once($CFG->dirroot . '/repository/lib.php');
-            $editabletypes = repository::get_editable_types($usercontext);
-            if ($usercontext->contextlevel == CONTEXT_USER && !empty($editabletypes)) {
+        if (!$currentuser && $usercontext->contextlevel == CONTEXT_USER) {
+            if (!$this->cache->cached('contexthasrepos'.$usercontext->id)) {
+                require_once($CFG->dirroot . '/repository/lib.php');
+                $editabletypes = repository::get_editable_types($usercontext);
+                $haseditabletypes = !empty($editabletypes);
+                unset($editabletypes);
+                $this->cache->set('contexthasrepos'.$usercontext->id, $haseditabletypes);
+            } else {
+                $haseditabletypes = $this->cache->{'contexthasrepos'.$usercontext->id};
+            }
+            if ($haseditabletypes) {
                 $url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$usercontext->id));
                 $usersetting->add(get_string('repositories', 'repository'), $url, self::TYPE_SETTING);
             }
@@ -3861,8 +3944,6 @@ class navigation_json {
      * @return string JSON
      */
     protected function convert_child($child, $depth=1) {
-        global $OUTPUT;
-
         if (!$child->display) {
             return '';
         }
@@ -3974,22 +4055,31 @@ class navigation_cache {
      *                cache
      * @param int $timeout The number of seconds to time the information out after
      */
-    public function __construct($area, $timeout=60) {
-        global $SESSION;
+    public function __construct($area, $timeout=1800) {
         $this->creation = time();
         $this->area = $area;
-
-        if (!isset($SESSION->navcache)) {
-            $SESSION->navcache = new stdClass;
+        $this->timeout = time() - $timeout;
+        if (rand(0,100) === 0) {
+            $this->garbage_collection();
         }
+    }
 
-        if (!isset($SESSION->navcache->{$area})) {
-            $SESSION->navcache->{$area} = array();
-        }
-        $this->session = &$SESSION->navcache->{$area};
-        $this->timeout = time()-$timeout;
-        if (rand(0,10)===0) {
-            $this->garbage_collection();
+    /**
+     * Used to set up the cache within the SESSION.
+     *
+     * This is called for each access and ensure that we don't put anything into the session before
+     * it is required.
+     */
+    protected function ensure_session_cache_initialised() {
+        global $SESSION;
+        if (empty($this->session)) {
+            if (!isset($SESSION->navcache)) {
+                $SESSION->navcache = new stdClass;
+            }
+            if (!isset($SESSION->navcache->{$this->area})) {
+                $SESSION->navcache->{$this->area} = array();
+            }
+            $this->session = &$SESSION->navcache->{$this->area};
         }
     }
 
@@ -4025,6 +4115,7 @@ class navigation_cache {
      */
     public function set($key, $information) {
         global $USER;
+        $this->ensure_session_cache_initialised();
         $information = serialize($information);
         $this->session[$key]= array(self::CACHETIME=>time(), self::CACHEUSERID=>$USER->id, self::CACHEVALUE=>$information);
     }
@@ -4036,6 +4127,7 @@ class navigation_cache {
      */
     public function cached($key) {
         global $USER;
+        $this->ensure_session_cache_initialised();
         if (!array_key_exists($key, $this->session) || !is_array($this->session[$key]) || $this->session[$key][self::CACHEUSERID]!=$USER->id || $this->session[$key][self::CACHETIME] < $this->timeout) {
             return false;
         }
@@ -4051,7 +4143,7 @@ class navigation_cache {
      *              serialised
      * @return bool If the value is the same false if it is not set or doesn't match
      */
-    public function compare($key, $value, $serialise=true) {
+    public function compare($key, $value, $serialise = true) {
         if ($this->cached($key)) {
             if ($serialise) {
                 $value = serialize($value);
@@ -4066,12 +4158,17 @@ class navigation_cache {
      * Wipes the entire cache, good to force regeneration
      */
     public function clear() {
-        $this->session = array();
+        global $SESSION;
+        unset($SESSION->navcache);
+        $this->session = null;
     }
     /**
      * Checks all cache entries and removes any that have expired, good ole cleanup
      */
     protected function garbage_collection() {
+        if (empty($this->session)) {
+            return true;
+        }
         foreach ($this->session as $key=>$cachedinfo) {
             if (is_array($cachedinfo) && $cachedinfo[self::CACHETIME]<$this->timeout) {
                 unset($this->session[$key]);
index b75d5cc..6622440 100644 (file)
@@ -368,12 +368,8 @@ class global_navigation_test extends UnitTestCase {
         $this->assertTrue($this->node->exposed_format_display_course_content('dummy'));
     }
     public function test_module_extends_navigation() {
-        $this->cache->test1_extends_navigation = true;
-        $this->cache->test2_extends_navigation = false;
         $this->assertTrue($this->node->exposed_module_extends_navigation('data'));
-        $this->assertTrue($this->node->exposed_module_extends_navigation('test1'));
-        $this->assertFalse($this->node->exposed_module_extends_navigation('test2'));
-        $this->assertFalse($this->node->exposed_module_extends_navigation('test3'));
+        $this->assertFalse($this->node->exposed_module_extends_navigation('test1'));
     }
 }