Merge branch 'MDL-28023_assignment_jabber' of git://github.com/andyjdavis/moodle
authorSam Hemelryk <sam@moodle.com>
Tue, 28 Jun 2011 09:04:55 +0000 (17:04 +0800)
committerSam Hemelryk <sam@moodle.com>
Tue, 28 Jun 2011 09:04:55 +0000 (17:04 +0800)
32 files changed:
backup/backup.class.php
course/switchrole.php
install/lang/zh_cn/moodle.php
lib/completionlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/mysqli_native_moodle_database.php
lib/navigationlib.php
lib/questionlib.php
lib/simpletest/testcompletionlib.php
lib/simpletest/testnavigationlib.php
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/quiz/lang/en/quiz.php
mod/quiz/report/grading/lang/en/quiz_grading.php
mod/quiz/report/overview/lang/en/quiz_overview.php
mod/quiz/report/responses/lang/en/quiz_responses.php
mod/quiz/report/statistics/db/upgrade.php
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/version.php
pluginfile.php
question/behaviour/adaptive/renderer.php
question/behaviour/adaptive/simpletest/testwalkthrough.php
question/editlib.php
question/export.php
question/format/xml/format.php
question/format/xml/simpletest/testxmlformat.php
question/import.php
question/type/random/questiontype.php
version.php

index f4c7425..5a1814d 100644 (file)
@@ -108,8 +108,8 @@ abstract class backup implements checksumable {
     const OPERATION_RESTORE ='restore';// We are performing one restore
 
     // Version (to keep CFG->backup_version (and release) updated automatically)
-    const VERSION = 2010111800;
-    const RELEASE = '2.0';
+    const VERSION = 2011063000;
+    const RELEASE = '2.1';
 }
 
 /*
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 a120e93..2ebef26 100644 (file)
@@ -29,6 +29,6 @@
  */
 
 $string['language'] = '语言';
-$string['next'] = '下一步';
+$string['next'] = '向后';
 $string['previous'] = '向前';
 $string['reload'] = '重新载入';
index 4c8467b..cce41d0 100644 (file)
@@ -935,13 +935,20 @@ class completion_info {
     function internal_set_data($cm, $data) {
         global $USER, $SESSION, $DB;
 
-        if ($data->id) {
-            // Has real (nonzero) id meaning that a database row exists
-            $DB->update_record('course_modules_completion', $data);
-        } else {
+        $transaction = $DB->start_delegated_transaction();
+        if (!$data->id) {
+            // Check there isn't really a row
+            $data->id = $DB->get_field('course_modules_completion', 'id',
+                    array('coursemoduleid'=>$data->coursemoduleid, 'userid'=>$data->userid));
+        }
+        if (!$data->id) {
             // Didn't exist before, needs creating
             $data->id = $DB->insert_record('course_modules_completion', $data);
+        } else {
+            // Has real (nonzero) id meaning that a database row exists, update
+            $DB->update_record('course_modules_completion', $data);
         }
+        $transaction->allow_commit();
 
         if ($data->userid == $USER->id) {
             $SESSION->completioncache[$cm->course][$cm->id] = $data;
index 05d99f8..407c6f9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20110526" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20110627" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       </KEYS>
       <INDEXES>
-        <INDEX NAME="coursemoduleid" UNIQUE="false" FIELDS="coursemoduleid" COMMENT="For quick access via course-module (e.g. when displaying course module settings page and we need to determine whether anyone has completed it)." NEXT="userid"/>
-        <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" COMMENT="Index on user ID. Used when obtaining completion information for normal course page view." PREVIOUS="coursemoduleid"/>
+        <INDEX NAME="coursemoduleid" UNIQUE="false" FIELDS="coursemoduleid" COMMENT="For quick access via course-module (e.g. when displaying course module settings page and we need to determine whether anyone has completed it)." NEXT="userid-coursemoduleid"/>
+        <INDEX NAME="userid-coursemoduleid" UNIQUE="true" FIELDS="userid, coursemoduleid" PREVIOUS="coursemoduleid"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="course_sections" COMMENT="to define the sections for each course" PREVIOUS="course_modules_completion" NEXT="course_request">
         <FIELD NAME="questiontextformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="questiontext" NEXT="generalfeedback"/>
         <FIELD NAME="generalfeedback" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="to store the question feedback" PREVIOUS="questiontextformat" NEXT="generalfeedbackformat"/>
         <FIELD NAME="generalfeedbackformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="generalfeedback" NEXT="defaultmark"/>
-        <FIELD NAME="defaultmark" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" DECIMALS="7" PREVIOUS="generalfeedbackformat" NEXT="penalty"/>
+        <FIELD NAME="defaultmark" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" DECIMALS="7" PREVIOUS="generalfeedbackformat" NEXT="penalty"/>
         <FIELD NAME="penalty" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="false" DEFAULT="0.3333333" SEQUENCE="false" DECIMALS="7" PREVIOUS="defaultmark" NEXT="qtype"/>
         <FIELD NAME="qtype" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false" PREVIOUS="penalty" NEXT="length"/>
         <FIELD NAME="length" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" PREVIOUS="qtype" NEXT="stamp"/>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="questionid"/>
         <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="id" NEXT="hint"/>
         <FIELD NAME="hint" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="The text of the feedback to be given." PREVIOUS="questionid" NEXT="hintformat"/>
-        <FIELD NAME="hintformat" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" DEFAULT="0" COMMENT="The format of the hint." PREVIOUS="hint" NEXT="shownumcorrect"/>
+        <FIELD NAME="hintformat" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="The format of the hint." PREVIOUS="hint" NEXT="shownumcorrect"/>
         <FIELD NAME="shownumcorrect" TYPE="int" LENGTH="1" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Whether the feedback should include a message about how many things the student got right. This is only applicable to certain question types (for example matching or multiple choice multiple-response)." PREVIOUS="hintformat" NEXT="clearwrong"/>
         <FIELD NAME="clearwrong" TYPE="int" LENGTH="1" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Whether any wrong choices should be cleared before the next try. Whether this is applicable, and what it means, depends on the question type, as with the shownumright option." PREVIOUS="shownumcorrect" NEXT="options"/>
         <FIELD NAME="options" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="A space for any other question-type specific options." PREVIOUS="clearwrong"/>
         <FIELD NAME="slot" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="Used to number the questions in one attempt sequentially." PREVIOUS="questionusageid" NEXT="behaviour"/>
         <FIELD NAME="behaviour" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="The name of the question behaviour that is managing this question attempt." PREVIOUS="slot" NEXT="questionid"/>
         <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="The id of the question being attempted. Foreign key references question.id." PREVIOUS="behaviour" NEXT="variant"/>
-        <FIELD NAME="variant" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" DEFAULT="1" COMMENT="The variant of the qusetion being used." PREVIOUS="questionid" NEXT="maxmark"/>
+        <FIELD NAME="variant" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" COMMENT="The variant of the qusetion being used." PREVIOUS="questionid" NEXT="maxmark"/>
         <FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" DECIMALS="7" COMMENT="The grade this question is marked out of in this attempt." PREVIOUS="variant" NEXT="minfraction"/>
         <FIELD NAME="minfraction" TYPE="number" LENGTH="12" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" DECIMALS="7" COMMENT="Some questions can award negative marks. This indicates the most negative mark that can be awarded, on the faction scale where the maximum positive mark is 1." PREVIOUS="maxmark" NEXT="flagged"/>
         <FIELD NAME="flagged" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this question has been flagged within the attempt." PREVIOUS="minfraction" NEXT="questionsummary"/>
index a8bd913..b12db58 100644 (file)
@@ -6558,6 +6558,75 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
         upgrade_main_savepoint(true, 2011062000.01);
     }
 
+    // Signed fixes - MDL-28032
+    if ($oldversion < 2011062400.02) {
+
+        // Changing sign of field defaultmark on table question to unsigned
+        $table = new xmldb_table('question');
+        $field = new xmldb_field('defaultmark', XMLDB_TYPE_NUMBER, '12, 7', null, XMLDB_NOTNULL, null, '1', 'generalfeedbackformat');
+
+        // Launch change of sign for field defaultmark
+        $dbman->change_field_unsigned($table, $field);
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011062400.02);
+    }
+
+    if ($oldversion < 2011062400.03) {
+        // Completion system has issue in which possible duplicate rows are
+        // added to the course_modules_completion table. This change deletes
+        // the older version of duplicate rows and replaces an index with a
+        // unique one so it won't happen again.
+
+        // This would have been a single query but because MySQL is a PoS
+        // and can't do subqueries in DELETE, I have made it into two. The
+        // system is unlikely to run out of memory as only IDs are stored in
+        // the array.
+
+        // Find all rows cmc1 where there is another row cmc2 with the
+        // same user id and the same coursemoduleid, but a higher id (=> newer,
+        // meaning that cmc1 is an older row).
+        $rs = $DB->get_recordset_sql("
+SELECT DISTINCT
+    cmc1.id
+FROM
+    {course_modules_completion} cmc1
+    JOIN {course_modules_completion} cmc2
+        ON cmc2.userid = cmc1.userid
+        AND cmc2.coursemoduleid = cmc1.coursemoduleid
+        AND cmc2.id > cmc1.id");
+        $deleteids = array();
+        foreach ($rs as $row) {
+            $deleteids[] = $row->id;
+        }
+        $rs->close();
+        // Note: SELECT part performance tested on table with ~7m
+        // rows of which ~15k match, only took 30 seconds so probably okay.
+
+        // Delete all those rows
+        $DB->delete_records_list('course_modules_completion', 'id', $deleteids);
+
+        // Define index userid (not unique) to be dropped form course_modules_completion
+        $table = new xmldb_table('course_modules_completion');
+        $index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+
+        // Conditionally launch drop index userid
+        if ($dbman->index_exists($table, $index)) {
+            $dbman->drop_index($table, $index);
+        }
+
+        // Define index userid-coursemoduleid (unique) to be added to course_modules_completion
+        $index = new xmldb_index('userid-coursemoduleid', XMLDB_INDEX_UNIQUE,
+                array('userid', 'coursemoduleid'));
+
+        // Conditionally launch add index userid-coursemoduleid
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        upgrade_main_savepoint(true, 2011062400.03);
+    }
+
     return true;
 }
 
index 243f29d..d2693bf 100644 (file)
@@ -499,7 +499,7 @@ class mysqli_native_moodle_database extends moodle_database {
                 $info->has_default   = is_null($info->default_value) ? false : true;
                 $info->primary_key   = ($rawcolumn->key === 'PRI');
                 $info->binary        = false;
-                $info->unsigned      = null;
+                $info->unsigned      = (stripos($rawcolumn->type, 'unsigned') !== false);
                 $info->auto_increment= false;
                 $info->unique        = null;
 
index 1c24fa2..3efae16 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,31 @@ 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 = '';
         }
 
         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 +1379,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 +1503,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 +1543,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 +1642,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 +1655,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 +1716,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 +1781,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 +1795,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 +1855,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) {
@@ -1871,17 +1956,22 @@ class global_navigation extends navigation_node {
 
             // Check the number of nodes in the report node... if there are none remove
             // the node
-            if (count($reporttab->children)===0) {
-                $usernode->remove_child($reporttab);
-            }
+            $reporttab->trim_if_empty();
         }
 
         // 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 +2045,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 +2087,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 +2119,27 @@ class global_navigation extends navigation_node {
             $url = new moodle_url('/course/view.php', array('id'=>$course->id));
         }
 
-        if ($displaycategories) {
-            // 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)) {
+        if (!$ismycourse && !$issite && !empty($course->category)) {
+            if (!empty($CFG->navshowcategories)) {
+                // We need to load the category structure for this course
                 $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;
-                    }
+            }
+            if (array_key_exists($course->category, $this->addedcategories)) {
+                $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 +2156,7 @@ class global_navigation extends navigation_node {
                 }
             }
         }
+
         return $coursenode;
     }
     /**
@@ -2356,7 +2420,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 +2457,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 +2476,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 +2730,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 +2738,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 +2998,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 +3042,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 +3052,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 +3163,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 +3190,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 +3211,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 +3251,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 +3474,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 +3487,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 +3504,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 +3567,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 +3649,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 +3939,6 @@ class navigation_json {
      * @return string JSON
      */
     protected function convert_child($child, $depth=1) {
-        global $OUTPUT;
-
         if (!$child->display) {
             return '';
         }
@@ -3909,7 +3985,7 @@ class navigation_json {
         $attributes['hidden'] = ($child->hidden);
         $attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY);
 
-        if (count($child->children)>0) {
+        if ($child->children->count() > 0) {
             $attributes['children'] = array();
             foreach ($child->children as $subchild) {
                 $attributes['children'][] = $this->convert_child($subchild, $depth+1);
@@ -3974,22 +4050,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 +4110,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 +4122,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 +4138,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 +4153,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 b1133e4..5c7d89f 100644 (file)
@@ -1434,6 +1434,10 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c
         return;
     }
 
+    if (($cat = $PAGE->url->param('cat')) && preg_match('~\d+,\d+~', $cat)) {
+        $params['cat'] = $cat;
+    }
+
     $questionnode = $navigationnode->add(get_string('questionbank', 'question'),
             new moodle_url('/question/edit.php', $params), navigation_node::TYPE_CONTAINER);
 
index 66fc9cb..f4288fd 100644 (file)
@@ -6,6 +6,7 @@ require_once($CFG->libdir.'/completionlib.php');
 
 global $DB;
 Mock::generate(get_class($DB), 'mock_database');
+Mock::generate('moodle_transaction', 'mock_transaction');
 
 Mock::generatePartial('completion_info','completion_cutdown',
     array('delete_all_state','get_tracked_users','update_state',
@@ -452,24 +453,40 @@ WHERE
     function test_internal_set_data() {
         global $DB,$SESSION;
 
-        $cm=(object)array('course'=>42,'id'=>13);
-        $c=new completion_info((object)array('id'=>42));
+        $cm = (object)array('course' => 42,'id' => 13);
+        $c = new completion_info((object)array('id' => 42));
 
         // 1) Test with new data
-        $data=(object)array('id'=>0,'userid'=>314159);
-        $DB->setReturnValueAt(0,'insert_record',4);
-        $DB->expectAt(0,'insert_record',array('course_modules_completion',$data));
-        $c->internal_set_data($cm,$data);
-        $this->assertEqual(4,$data->id);
-        $this->assertEqual(array(42=>array(13=>$data)),$SESSION->completioncache);
+        $data = (object)array('id'=>0, 'userid' => 314159, 'coursemoduleid' => 99);
+        $DB->setReturnValueAt(0, 'start_delegated_transaction', new mock_transaction());
+        $DB->setReturnValueAt(0, 'insert_record', 4);
+        $DB->expectAt(0, 'get_field', array('course_modules_completion', 'id',
+                array('coursemoduleid' => 99, 'userid' => 314159)));
+        $DB->expectAt(0, 'insert_record', array('course_modules_completion', $data));
+        $c->internal_set_data($cm, $data);
+        $this->assertEqual(4, $data->id);
+        $this->assertEqual(array(42 => array(13 => $data)), $SESSION->completioncache);
 
         // 2) Test with existing data and for different user (not cached)
         unset($SESSION->completioncache);
-        $d2=(object)array('id'=>7,'userid'=>17);
-        $DB->expectAt(0,'update_record',array('course_modules_completion',$d2));
-        $c->internal_set_data($cm,$d2);
+        $d2 = (object)array('id' => 7, 'userid' => 17, 'coursemoduleid' => 66);
+        $DB->setReturnValueAt(1, 'start_delegated_transaction', new mock_transaction());
+        $DB->expectAt(0,'update_record', array('course_modules_completion', $d2));
+        $c->internal_set_data($cm, $d2);
         $this->assertFalse(isset($SESSION->completioncache));
 
+        // 3) Test where it THINKS the data is new (from cache) but actually
+        // in the database it has been set since
+        // 1) Test with new data
+        $data = (object)array('id'=>0, 'userid' => 314159, 'coursemoduleid' => 99);
+        $DB->setReturnValueAt(2, 'start_delegated_transaction', new mock_transaction());
+        $DB->setReturnValueAt(1, 'get_field', 13);
+        $DB->expectAt(1, 'get_field', array('course_modules_completion', 'id',
+                array('coursemoduleid' => 99, 'userid' => 314159)));
+        $d3 = (object)array('id' => 13, 'userid' => 314159, 'coursemoduleid' => 99);
+        $DB->expectAt(1,'update_record', array('course_modules_completion', $d3));
+        $c->internal_set_data($cm, $data);
+
         $DB->tally();
     }
 
index b75d5cc..fdb5f4f 100644 (file)
@@ -316,14 +316,10 @@ class global_navigation_test extends UnitTestCase {
      * @var global_navigation
      */
     public $node;
-    protected $cache;
-    protected $modinfo5 = 'O:6:"object":6:{s:8:"courseid";s:1:"5";s:6:"userid";s:1:"2";s:8:"sections";a:1:{i:0;a:1:{i:0;s:3:"288";}}s:3:"cms";a:1:{i:288;O:6:"object":17:{s:2:"id";s:3:"288";s:8:"instance";s:2:"19";s:6:"course";s:1:"5";s:7:"modname";s:5:"forum";s:4:"name";s:10:"News forum";s:7:"visible";s:1:"1";s:10:"sectionnum";s:1:"0";s:9:"groupmode";s:1:"0";s:10:"groupingid";s:1:"0";s:16:"groupmembersonly";s:1:"0";s:6:"indent";s:1:"0";s:10:"completion";s:1:"0";s:5:"extra";s:0:"";s:4:"icon";s:0:"";s:11:"uservisible";b:1;s:9:"modplural";s:6:"Forums";s:9:"available";b:1;}}s:9:"instances";a:1:{s:5:"forum";a:1:{i:19;R:8;}}s:6:"groups";N;}';
-    protected $coursesections5 = 'a:5:{i:0;O:8:"stdClass":6:{s:7:"section";s:1:"0";s:2:"id";s:2:"14";s:6:"course";s:1:"5";s:7:"summary";N;s:8:"sequence";s:3:"288";s:7:"visible";s:1:"1";}i:1;O:8:"stdClass":6:{s:7:"section";s:1:"1";s:2:"id";s:2:"97";s:6:"course";s:1:"5";s:7:"summary";s:0:"";s:8:"sequence";N;s:7:"visible";s:1:"1";}i:2;O:8:"stdClass":6:{s:7:"section";s:1:"2";s:2:"id";s:2:"98";s:6:"course";s:1:"5";s:7:"summary";s:0:"";s:8:"sequence";N;s:7:"visible";s:1:"1";}i:3;O:8:"stdClass":6:{s:7:"section";s:1:"3";s:2:"id";s:2:"99";s:6:"course";s:1:"5";s:7:"summary";s:0:"";s:8:"sequence";N;s:7:"visible";s:1:"1";}i:4;O:8:"stdClass":6:{s:7:"section";s:1:"4";s:2:"id";s:3:"100";s:6:"course";s:1:"5";s:7:"summary";s:0:"";s:8:"sequence";N;s:7:"visible";s:1:"1";}}';
     public static $includecoverage = array('./lib/navigationlib.php');
     public static $excludecoverage = array();
 
     public function setUp() {
-        $this->cache = new navigation_cache('simpletest_nav');
         $this->node = new exposed_global_navigation();
         // Create an initial tree structure to work with
         $cat1 = $this->node->add('category 1', null, navigation_node::TYPE_CATEGORY, null, 'cat1');
@@ -335,45 +331,25 @@ class global_navigation_test extends UnitTestCase {
         $course1 = $sub2->add('course 1', null, navigation_node::TYPE_COURSE, null, 'course1');
         $course2 = $sub2->add('course 2', null, navigation_node::TYPE_COURSE, null, 'course2');
         $course3 = $sub2->add('course 3', null, navigation_node::TYPE_COURSE, null, 'course3');
-        $section1 = $course2->add('section 1', null, navigation_node::TYPE_COURSE, null, 'sec1');
-        $section2 = $course2->add('section 2', null, navigation_node::TYPE_COURSE, null, 'sec2');
-        $section3 = $course2->add('section 3', null, navigation_node::TYPE_COURSE, null, 'sec3');
+        $section1 = $course2->add('section 1', null, navigation_node::TYPE_SECTION, null, 'sec1');
+        $section2 = $course2->add('section 2', null, navigation_node::TYPE_SECTION, null, 'sec2');
+        $section3 = $course2->add('section 3', null, navigation_node::TYPE_SECTION, null, 'sec3');
         $act1 = $section2->add('activity 1', null, navigation_node::TYPE_ACTIVITY, null, 'act1');
         $act2 = $section2->add('activity 2', null, navigation_node::TYPE_ACTIVITY, null, 'act2');
         $act3 = $section2->add('activity 3', null, navigation_node::TYPE_ACTIVITY, null, 'act3');
         $res1 = $section2->add('resource 1', null, navigation_node::TYPE_RESOURCE, null, 'res1');
         $res2 = $section2->add('resource 2', null, navigation_node::TYPE_RESOURCE, null, 'res2');
         $res3 = $section2->add('resource 3', null, navigation_node::TYPE_RESOURCE, null, 'res3');
-
-        $this->cache->clear();
-        $this->cache->modinfo5 = unserialize($this->modinfo5);
-        $this->cache->coursesections5 = unserialize($this->coursesections5);
-        $this->cache->canviewhiddenactivities = true;
-        $this->cache->canviewhiddensections = true;
-        $this->cache->canviewhiddencourses = true;
-        $sub2->add('Test Course 5', new moodle_url('http://moodle.org'),navigation_node::TYPE_COURSE,null,'5');
-    }
-    public function test_load_generic_course_sections() {
-        $coursenode = $this->node->find('5', navigation_node::TYPE_COURSE);
-        $course = new stdClass;
-        $course->id = '5';
-        $course->numsections = 10;
-        $course->modinfo = $this->modinfo5;
-        $this->node->load_generic_course_sections($course, $coursenode, 'topic', 'topic');
-        $this->assertEqual($coursenode->children->count(),1);
     }
+
     public function test_format_display_course_content() {
         $this->assertTrue($this->node->exposed_format_display_course_content('topic'));
         $this->assertFalse($this->node->exposed_format_display_course_content('scorm'));
         $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'));
     }
 }
 
index af2dfcb..30402b2 100644 (file)
@@ -213,11 +213,7 @@ class feedback_item_multichoice extends feedback_item_base {
             $analysedVals = $analysedItem[2];
             $pixnr = 0;
             foreach($analysedVals as $val) {
-                if( function_exists("bcmod")) {
-                    $intvalue = bcmod($pixnr, 10);
-                }else {
-                    $intvalue = 0;
-                }
+                $intvalue = $pixnr % 10;
                 $pix = "pics/$intvalue.gif";
                 $pixnr++;
                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
index 77a2b04..41764e9 100644 (file)
@@ -184,11 +184,7 @@ class feedback_item_multichoicerated extends feedback_item_base {
             $pixnr = 0;
             $avg = 0.0;
             foreach($analysedVals as $val) {
-                if( function_exists("bcmod")) {
-                    $intvalue = bcmod($pixnr, 10);
-                }else {
-                    $intvalue = 0;
-                }
+                $intvalue = $pixnr % 10;
                 $pix = "pics/$intvalue.gif";
                 $pixnr++;
                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
index 4a2a3b1..abc853a 100644 (file)
@@ -34,7 +34,7 @@ $string['actionaftercorrectanswer_help'] = 'After answering a question correctly
 $string['actions'] = 'Actions';
 $string['activitylink'] = 'Link to an activity';
 $string['activitylink_help'] = 'To provide a link at the end of the lesson to another activity in the course, select the activity from the dropdown list.';
-$string['activitylinkname'] = 'Go to: {$a}';
+$string['activitylinkname'] = 'Go to {$a}';
 $string['addabranchtable'] = 'Add a content page';
 $string['addanendofbranch'] = 'Add an end of branch';
 $string['addanewpage'] = 'Add a new page';
index b4fb01e..84a8661 100644 (file)
@@ -1280,7 +1280,7 @@ class lesson extends lesson_base {
                 $instancename = $DB->get_field($modname, 'name', array('id' => $module->instance));
                 if ($instancename) {
                     return html_writer::link(new moodle_url('/mod/'.$modname.'/view.php', array('id'=>$this->properties->activitylink)),
-                        get_string('returnto', 'lesson', get_string('activitylinkname', 'lesson', $instancename)),
+                        get_string('activitylinkname', 'lesson', $instancename),
                         array('class'=>'centerpadded lessonbutton standardbutton'));
                 }
             }
index 6deab91..cf79766 100644 (file)
@@ -727,6 +727,8 @@ $string['submitallandfinish'] = 'Submit all and finish';
 $string['subneterror'] = 'Sorry, this quiz has been locked so that it is only accessible from certain locations.  Currently your computer is not one of those allowed to use this quiz.';
 $string['subnetnotice'] = 'This quiz has been locked so that it is only accessible from certain locations. Your computer is not on an allowed subnet. As teacher you are allowed to preview anyway.';
 $string['subnetwrong'] = 'This quiz is only accessible from certain locations, and this computer is not on the allowed list.';
+$string['subplugintype_quiz'] = 'Report';
+$string['subplugintype_quiz_plural'] = 'Reports';
 $string['substitutedby'] = 'will be substituted by';
 $string['summaryofattempt'] = 'Summary of attempt';
 $string['summaryofattempts'] = 'Summary of your previous attempts';
index 676d99e..2ba6063 100644 (file)
@@ -66,6 +66,7 @@ $string['inprogress'] = 'In progress';
 $string['noquestionsfound'] = 'No manually graded questions found';
 $string['options'] = 'Options';
 $string['orderattempts'] = 'Order attempts';
+$string['pluginname'] = 'Manual grading';
 $string['qno'] = 'Q #';
 $string['questionname'] = 'Question name';
 $string['questionsperpage'] = 'Questions per page';
index 21a90eb..367ec4e 100644 (file)
@@ -52,6 +52,7 @@ $string['overviewreport'] = 'Grades report';
 $string['overviewreportgraph'] = 'Overall number of students achieving grade ranges';
 $string['overviewreportgraphgroup'] = 'Number of students in group \'{$a}\' achieving grade ranges';
 $string['pagesize'] = 'Page size';
+$string['pluginname'] = 'Grades';
 $string['preferencespage'] = 'Preferences just for this page';
 $string['preferencessave'] = 'Show report';
 $string['preferencesuser'] = 'Your preferences for this report';
index edd4a42..baa938d 100644 (file)
@@ -27,6 +27,7 @@
 $string['cannotloadoptions'] = 'Could not load question options';
 $string['include'] = 'Include';
 $string['pagesize'] = 'Page size';
+$string['pluginname'] = 'Responses';
 $string['reportresponses'] = 'Responses';
 $string['responses'] = 'Responses';
 $string['responsesdownload'] = 'Responses download';
index 78944f3..a785346 100644 (file)
@@ -380,6 +380,20 @@ function xmldb_quiz_statistics_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2011021500, 'quiz', 'statistics');
     }
 
+    // Signed fixes - MDL-28032
+    if ($oldversion < 2011062600) {
+
+        // Changing sign of field maxmark on table quiz_question_statistics to signed
+        $table = new xmldb_table('quiz_question_statistics');
+        $field = new xmldb_field('maxmark', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null, 'subquestions');
+
+        // Launch change of sign for field maxmark
+        $dbman->change_field_unsigned($table, $field);
+
+        // statistics savepoint reached
+        upgrade_plugin_savepoint(true, 2011062600, 'quiz', 'statistics');
+    }
+
     return true;
 }
 
index 2efbe23..1ca0487 100644 (file)
@@ -71,6 +71,7 @@ Our equation for effective question weight cannot be calculated in this case. Th
 If you edit a quiz and give these question(s) with negative covariance a max grade of zero then the effective question weight of these questions will be zero and the real effective question weight of other questions will be as calculated now.';
 $string['nostudentsingroup'] = 'There are no students in this group yet';
 $string['optiongrade'] = 'Partial credit';
+$string['pluginname'] = 'Statistics';
 $string['position'] = 'Position';
 $string['positions'] = 'Position(s)';
 $string['questioninformation'] = 'Question information';
index 0359678..e11e2fc 100644 (file)
@@ -25,5 +25,5 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2011051200;
+$plugin->version  = 2011062600;
 $plugin->requires = 2011060313;
index e749921..cc855cc 100644 (file)
@@ -294,6 +294,9 @@ if ($component === 'blog') {
 // ========================================================================================================================
 } else if ($component === 'user') {
     if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) {
+        // XXX: pix_url will initialize $PAGE, so we have to set up context here
+        // this temp hack should be fixed by better solution
+        $PAGE->set_context(get_system_context());
         if (!empty($CFG->forcelogin) and !isloggedin()) {
             // protect images if login required and not logged in;
             // do not use require_login() because it is expensive and not suitable here anyway
index f9faf30..da1a632 100644 (file)
@@ -77,7 +77,7 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
 
         $gradingdetails = get_string('gradingdetails', 'qbehaviour_adaptive', $mark);
 
-        $gradingdetails .= $this->penalty_info($qa, $mark);
+        $gradingdetails .= $this->penalty_info($qa, $mark, $options);
 
         $output = '';
         $output .= html_writer::tag('div', get_string($class, 'question'),
@@ -87,7 +87,14 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
         return $output;
     }
 
-    protected function penalty_info($qa, $mark) {
+    /**
+     * Display the information about the penalty calculations.
+     * @param question_attempt $qa the question attempt.
+     * @param object $mark contains information about the current mark.
+     * @param question_display_options $options display options.
+     */
+    protected function penalty_info(question_attempt $qa, $mark,
+            question_display_options $options) {
         if (!$qa->get_question()->penalty) {
             return '';
         }
@@ -102,7 +109,7 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
         // penalty is relevant only if the answer is not correct and further attempts are possible
         if (!$qa->get_state()->is_finished()) {
             $output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
-                    $qa->get_question()->penalty);
+                    format_float($qa->get_question()->penalty, $options->markdp));
         }
 
         return $output;
index 88fd82c..26eb641 100644 (file)
@@ -101,7 +101,10 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_mc_radio_expectation($rightindex, true, true),
                 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
                 $this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false),
-                $this->get_contains_correct_expectation());
+                $this->get_contains_correct_expectation(),
+                new PatternExpectation('/' . preg_quote(
+                        get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
+                            format_float($mc->penalty, $this->displayoptions->markdp))) . '/'));
         $this->assertEqual('A',
                 $this->quba->get_response_summary($this->slot));
 
index 9848e87..afb8891 100644 (file)
@@ -1595,9 +1595,6 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
             $thiscontext = null;
         }
     }
-    if (strpos($baseurl, '/question/') === 0) {
-        navigation_node::override_active_url($thispageurl);
-    }
 
     if ($thiscontext){
         $contexts = new question_edit_contexts($thiscontext);
@@ -1607,16 +1604,14 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
         $contexts = null;
     }
 
-
-
     $pagevars['qpage'] = optional_param('qpage', -1, PARAM_INT);
 
     //pass 'cat' from page to page and when 'category' comes from a drop down menu
     //then we also reset the qpage so we go to page 1 of
     //a new cat.
-    $pagevars['cat'] = optional_param('cat', 0, PARAM_SEQUENCE);// if empty will be set up later
-    if  ($category = optional_param('category', 0, PARAM_SEQUENCE)){
-        if ($pagevars['cat'] != $category){ // is this a move to a new category?
+    $pagevars['cat'] = optional_param('cat', 0, PARAM_SEQUENCE); // if empty will be set up later
+    if ($category = optional_param('category', 0, PARAM_SEQUENCE)) {
+        if ($pagevars['cat'] != $category) { // is this a move to a new category?
             $pagevars['cat'] = $category;
             $pagevars['qpage'] = 0;
         }
@@ -1624,6 +1619,10 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
     if ($pagevars['cat']){
         $thispageurl->param('cat', $pagevars['cat']);
     }
+    if (strpos($baseurl, '/question/') === 0) {
+        navigation_node::override_active_url($thispageurl);
+    }
+
     if ($pagevars['qpage'] > -1) {
         $thispageurl->param('qpage', $pagevars['qpage']);
     } else {
index 16000f7..dec240a 100644 (file)
@@ -36,16 +36,11 @@ list($thispageurl, $contexts, $cmid, $cm, $module, $pagevars) =
 // get display strings
 $strexportquestions = get_string('exportquestions', 'question');
 
-// make sure we are using the user's most recent category choice
-if (empty($categoryid)) {
-    $categoryid = $pagevars['cat'];
-}
-
 list($catid, $catcontext) = explode(',', $pagevars['cat']);
 $category = $DB->get_record('question_categories', array("id" => $catid, 'contextid' => $catcontext), '*', MUST_EXIST);
 
 /// Header
-$PAGE->set_url($thispageurl->out());
+$PAGE->set_url($thispageurl);
 $PAGE->set_title($strexportquestions);
 $PAGE->set_heading($COURSE->fullname);
 echo $OUTPUT->header();
index 21821a0..ca1af3a 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->libdir . '/xmlize.php');
+if (!class_exists('qformat_default')) {
+    // This is ugly, but this class is also (ab)used by mod/lesson, which defines
+    // a different base class in mod/lesson/format.php. Thefore, we can only
+    // include the proper base class conditionally like this. (We have to include
+    // the base class like this, otherwise it breaks third-party question types.)
+    // This may be reviewd, and a better fix found one day.
+    require_once($CFG->dirroot . '/question/format.php');
+}
 
 
 /**
index d45afd2..9ab295f 100644 (file)
@@ -27,7 +27,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->libdir . '/questionlib.php');
-require_once($CFG->dirroot . '/question/format.php');
 require_once($CFG->dirroot . '/question/format/xml/format.php');
 
 
index 56e8892..444d32f 100644 (file)
@@ -60,7 +60,7 @@ if ($contexts === null) { // need to get the course from the chosen category
     $contexts->require_one_edit_tab_cap($edittab);
 }
 
-$PAGE->set_url($thispageurl->out());
+$PAGE->set_url($thispageurl);
 
 $import_form = new question_import_form($thispageurl, array('contexts'=>$contexts->having_one_edit_tab_cap('import'),
                                                     'defaultcategory'=>$pagevars['cat']));
index 203dffc..95bb810 100644 (file)
@@ -26,6 +26,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once($CFG->dirroot . '/question/type/questiontypebase.php');
+
 
 /**
  * The random question type.
index 063c7a0..2504d70 100644 (file)
@@ -31,10 +31,10 @@ defined('MOODLE_INTERNAL') || die();
 
 
 
-$version  = 2011062400.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2011062700.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.1beta (Build: 20110624)'; // Human-friendly version name
+$release  = '2.1beta (Build: 20110627)'; // Human-friendly version name
 
 $maturity = MATURITY_BETA;              // this version's maturity level