diff --git a/lib/datalib.php b/lib/datalib.php
index 3adc9e9..7b7b131 100644
--- a/lib/datalib.php
+++ b/lib/datalib.php
@@ -57,9 +57,7 @@ function get_admin() {
     static $mainadmin = null;
     static $prevadmins = null;
 
-    if (empty($CFG->siteadmins)) {
-        // Should not happen on an ordinary site.
-        // It does however happen during unit tests.
+    if (empty($CFG->siteadmins)) {  // Should not happen on an ordinary site.
         return false;
     }
 
@@ -191,95 +189,6 @@ function search_users($courseid, $groupid, $searchtext, $sort='', array $excepti
 }
 
 /**
- * Returns SQL used to search through user table to find users (in a query
- * which may also join and apply other conditions).
- *
- * You can combine this SQL with an existing query by adding 'AND $sql' to the
- * WHERE clause of your query (where $sql is the first element in the array
- * returned by this function), and merging in the $params array to the parameters
- * of your query (where $params is the second element). Your query should use
- * named parameters such as :param, rather than the question mark style.
- *
- * There are examples of basic usage in the unit test for this function.
- *
- * @param string $search the text to search for (empty string = find all)
- * @param string $u the table alias for the user table in the query being
- *     built. May be ''.
- * @param bool $searchanywhere If true (default), searches in the middle of
- *     names, otherwise only searches at start
- * @param array $extrafields Array of extra user fields to include in search
- * @param array $exclude Array of user ids to exclude (empty = don't exclude)
- * @param array $includeonly If specified, only returns users that have ids
- *     incldued in this array (empty = don't restrict)
- * @return array an array with two elements, a fragment of SQL to go in the
- *     where clause the query, and an associative array containing any required
- *     parameters (using named placeholders).
- */
-function users_search_sql($search, $u = 'u', $searchanywhere = true, array $extrafields = array(),
-        array $exclude = null, array $includeonly = null) {
-    global $DB, $CFG;
-    $params = array();
-    $tests = array();
-
-    if ($u) {
-        $u .= '.';
-    }
-
-    // If we have a $search string, put a field LIKE '$search%' condition on each field.
-    if ($search) {
-        $conditions = array(
-            $DB->sql_fullname($u . 'firstname', $u . 'lastname'),
-            $conditions[] = $u . 'lastname'
-        );
-        foreach ($extrafields as $field) {
-            $conditions[] = $u . $field;
-        }
-        if ($searchanywhere) {
-            $searchparam = '%' . $search . '%';
-        } else {
-            $searchparam = $search . '%';
-        }
-        $i = 0;
-        foreach ($conditions as $key => $condition) {
-            $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false, false);
-            $params["con{$i}00"] = $searchparam;
-            $i++;
-        }
-        $tests[] = '(' . implode(' OR ', $conditions) . ')';
-    }
-
-    // Add some additional sensible conditions.
-    $tests[] = $u . "id <> :guestid";
-    $params['guestid'] = $CFG->siteguest;
-    $tests[] = $u . 'deleted = 0';
-    $tests[] = $u . 'confirmed = 1';
-
-    // If we are being asked to exclude any users, do that.
-    if (!empty($exclude)) {
-        list($usertest, $userparams) = $DB->get_in_or_equal($exclude, SQL_PARAMS_NAMED, 'ex', false);
-        $tests[] = $u . 'id ' . $usertest;
-        $params = array_merge($params, $userparams);
-    }
-
-    // If we are validating a set list of userids, add an id IN (...) test.
-    if (!empty($includeonly)) {
-        list($usertest, $userparams) = $DB->get_in_or_equal($includeonly, SQL_PARAMS_NAMED, 'val');
-        $tests[] = $u . 'id ' . $usertest;
-        $params = array_merge($params, $userparams);
-    }
-
-    // In case there are no tests, add one result (this makes it easier to combine
-    // this with an existing query as you can always add AND $sql).
-    if (empty($tests)) {
-        $tests[] = '1 = 1';
-    }
-
-    // Combing the conditions and return.
-    return array(implode(' AND ', $tests), $params);
-}
-
-
-/**
  * This function generates the standard ORDER BY clause for use when generating
  * lists of users. If you don't have a reason to use a different order, then
  * you should use this method to generate the order when displaying lists of users.
@@ -446,7 +355,7 @@ function get_users($get=true, $search='', $confirmed=false, array $exceptions=nu
 
 
 /**
- * Return filtered (if provided) list of users in site, except guest and deleted users.
+ * @todo Finish documenting this function
  *
  * @param string $sort An SQL field to sort by
  * @param string $dir The sort direction ASC|DESC
@@ -457,19 +366,19 @@ function get_users($get=true, $search='', $confirmed=false, array $exceptions=nu
  * @param string $lastinitial Users whose last name starts with $lastinitial
  * @param string $extraselect An additional SQL select statement to append to the query
  * @param array $extraparams Additional parameters to use for the above $extraselect
- * @param stdClass $extracontext If specified, will include user 'extra fields'
+ * @param object $extracontext If specified, will include user 'extra fields'
  *   as appropriate for current user and given context
  * @return array Array of {@link $USER} records
  */
 function get_users_listing($sort='lastaccess', $dir='ASC', $page=0, $recordsperpage=0,
                            $search='', $firstinitial='', $lastinitial='', $extraselect='',
                            array $extraparams=null, $extracontext = null) {
-    global $DB, $CFG;
+    global $DB;
 
     $fullname  = $DB->sql_fullname();
 
-    $select = "deleted <> 1 AND id <> :guestid";
-    $params = array('guestid' => $CFG->siteguest);
+    $select = "deleted <> 1";
+    $params = array();
 
     if (!empty($search)) {
         $search = trim($search);
@@ -573,7 +482,7 @@ function get_site() {
  * @param string $fields The additional fields to return
  * @return array Array of courses
  */
-function get_courses($categoryid="all", $sort="c.sortorder ASC", $fields="c.*") {
+function get_courses($categoryid="all", $sort="c.sortorder ASC", $fields="c.*",$limitfrom=0,$limitnum=0) {
 
     global $USER, $CFG, $DB;
 
@@ -603,7 +512,7 @@ function get_courses($categoryid="all", $sort="c.sortorder ASC", $fields="c.*")
               $sortstatement";
 
     // pull out all course matching the cat
-    if ($courses = $DB->get_records_sql($sql, $params)) {
+    if ($courses = $DB->get_records_sql($sql, $params,$limitfrom,$limitnum)) {
 
         // loop throught them
         foreach ($courses as $course) {
@@ -649,7 +558,7 @@ function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c
     $params = array();
 
     $categoryselect = "";
-    if ($categoryid !== "all" && is_numeric($categoryid)) {
+    if ($categoryid != "all" && is_numeric($categoryid)) {
         $categoryselect = "WHERE c.category = :catid";
         $params['catid'] = $categoryid;
     } else {
@@ -695,6 +604,226 @@ function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c
 }
 
 /**
+ * Retrieve course records with the course managers and other related records
+ * that we need for print_course(). This allows print_courses() to do its job
+ * in a constant number of DB queries, regardless of the number of courses,
+ * role assignments, etc.
+ *
+ * The returned array is indexed on c.id, and each course will have
+ * - $course->managers - array containing RA objects that include a $user obj
+ *                       with the minimal fields needed for fullname()
+ *
+ * @global object
+ * @global object
+ * @global object
+ * @uses CONTEXT_COURSE
+ * @uses CONTEXT_SYSTEM
+ * @uses CONTEXT_COURSECAT
+ * @uses SITEID
+ * @param int|string $categoryid Either the categoryid for the courses or 'all'
+ * @param string $sort A SQL sort field and direction
+ * @param array $fields An array of additional fields to fetch
+ * @return array
+ */
+function get_courses_wmanagers($categoryid=0, $sort="c.sortorder ASC", $fields=array()) {
+    /*
+     * The plan is to
+     *
+     * - Grab the courses JOINed w/context
+     *
+     * - Grab the interesting course-manager RAs
+     *   JOINed with a base user obj and add them to each course
+     *
+     * So as to do all the work in 2 DB queries. The RA+user JOIN
+     * ends up being pretty expensive if it happens over _all_
+     * courses on a large site. (Are we surprised!?)
+     *
+     * So this should _never_ get called with 'all' on a large site.
+     *
+     */
+    global $USER, $CFG, $DB;
+
+    $params = array();
+    $allcats = false; // bool flag
+    if ($categoryid === 'all') {
+        $categoryclause   = '';
+        $allcats = true;
+    } elseif (is_numeric($categoryid)) {
+        $categoryclause = "c.category = :catid";
+        $params['catid'] = $categoryid;
+    } else {
+        debugging("Could not recognise categoryid = $categoryid");
+        $categoryclause = '';
+    }
+
+    $basefields = array('id', 'category', 'sortorder',
+                        'shortname', 'fullname', 'idnumber',
+                        'startdate', 'visible',
+                        'newsitems', 'groupmode', 'groupmodeforce');
+
+    if (!is_null($fields) && is_string($fields)) {
+        if (empty($fields)) {
+            $fields = $basefields;
+        } else {
+            // turn the fields from a string to an array that
+            // get_user_courses_bycap() will like...
+            $fields = explode(',',$fields);
+            $fields = array_map('trim', $fields);
+            $fields = array_unique(array_merge($basefields, $fields));
+        }
+    } elseif (is_array($fields)) {
+        $fields = array_merge($basefields,$fields);
+    }
+    $coursefields = 'c.' .join(',c.', $fields);
+
+    if (empty($sort)) {
+        $sortstatement = "";
+    } else {
+        $sortstatement = "ORDER BY $sort";
+    }
+
+    $where = 'WHERE c.id != ' . SITEID;
+    if ($categoryclause !== ''){
+        $where = "$where AND $categoryclause";
+    }
+
+    // pull out all courses matching the cat
+    list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+    $sql = "SELECT $coursefields $ccselect
+              FROM {course} c
+           $ccjoin
+               $where
+               $sortstatement";
+
+    $catpaths = array();
+    $catpath  = NULL;
+    if ($courses = $DB->get_records_sql($sql, $params)) {
+        // loop on courses materialising
+        // the context, and prepping data to fetch the
+        // managers efficiently later...
+        foreach ($courses as $k => $course) {
+            context_instance_preload($course);
+            $coursecontext = context_course::instance($course->id);
+            $courses[$k] = $course;
+            $courses[$k]->managers = array();
+            if ($allcats === false) {
+                // single cat, so take just the first one...
+                if ($catpath === NULL) {
+                    $catpath = preg_replace(':/\d+$:', '', $coursecontext->path);
+                }
+            } else {
+                // chop off the contextid of the course itself
+                // like dirname() does...
+                $catpaths[] = preg_replace(':/\d+$:', '', $coursecontext->path);
+            }
+        }
+    } else {
+        return array(); // no courses!
+    }
+
+    $CFG->coursecontact = trim($CFG->coursecontact);
+    if (empty($CFG->coursecontact)) {
+        return $courses;
+    }
+
+    $managerroles = explode(',', $CFG->coursecontact);
+    $catctxids = '';
+    if (count($managerroles)) {
+        if ($allcats === true) {
+            $catpaths  = array_unique($catpaths);
+            $ctxids = array();
+            foreach ($catpaths as $cpath) {
+                $ctxids = array_merge($ctxids, explode('/',substr($cpath,1)));
+            }
+            $ctxids = array_unique($ctxids);
+            $catctxids = implode( ',' , $ctxids);
+            unset($catpaths);
+            unset($cpath);
+        } else {
+            // take the ctx path from the first course
+            // as all categories will be the same...
+            $catpath = substr($catpath,1);
+            $catpath = preg_replace(':/\d+$:','',$catpath);
+            $catctxids = str_replace('/',',',$catpath);
+        }
+        if ($categoryclause !== '') {
+            $categoryclause = "AND $categoryclause";
+        }
+        /*
+         * Note: Here we use a LEFT OUTER JOIN that can
+         * "optionally" match to avoid passing a ton of context
+         * ids in an IN() clause. Perhaps a subselect is faster.
+         *
+         * In any case, this SQL is not-so-nice over large sets of
+         * courses with no $categoryclause.
+         *
+         */
+        $sql = "SELECT ctx.path, ctx.instanceid, ctx.contextlevel,
+                       r.id AS roleid, r.name AS rolename, r.shortname AS roleshortname,
+                       rn.name AS rolecoursealias, u.id AS userid, u.firstname, u.lastname
+                  FROM {role_assignments} ra
+                  JOIN {context} ctx ON ra.contextid = ctx.id
+                  JOIN {user} u ON ra.userid = u.id
+                  JOIN {role} r ON ra.roleid = r.id
+             LEFT JOIN {role_names} rn ON (rn.contextid = ctx.id AND rn.roleid = r.id)
+                  LEFT OUTER JOIN {course} c
+                       ON (ctx.instanceid=c.id AND ctx.contextlevel=".CONTEXT_COURSE.")
+                WHERE ( c.id IS NOT NULL";
+        // under certain conditions, $catctxids is NULL
+        if($catctxids == NULL){
+            $sql .= ") ";
+        }else{
+            $sql .= " OR ra.contextid  IN ($catctxids) )";
+        }
+
+        $sql .= "AND ra.roleid IN ({$CFG->coursecontact})
+                      $categoryclause
+                ORDER BY r.sortorder ASC, ctx.contextlevel ASC, ra.sortorder ASC";
+        $rs = $DB->get_recordset_sql($sql, $params);
+
+        // This loop is fairly stupid as it stands - might get better
+        // results doing an initial pass clustering RAs by path.
+        foreach($rs as $ra) {
+            $user = new stdClass;
+            $user->id        = $ra->userid;    unset($ra->userid);
+            $user->firstname = $ra->firstname; unset($ra->firstname);
+            $user->lastname  = $ra->lastname;  unset($ra->lastname);
+            $ra->user = $user;
+            if ($ra->contextlevel == CONTEXT_SYSTEM) {
+                foreach ($courses as $k => $course) {
+                    $courses[$k]->managers[] = $ra;
+                }
+            } else if ($ra->contextlevel == CONTEXT_COURSECAT) {
+                if ($allcats === false) {
+                    // It always applies
+                    foreach ($courses as $k => $course) {
+                        $courses[$k]->managers[] = $ra;
+                    }
+                } else {
+                    foreach ($courses as $k => $course) {
+                        $coursecontext = context_course::instance($course->id);
+                        // Note that strpos() returns 0 as "matched at pos 0"
+                        if (strpos($coursecontext->path, $ra->path.'/') === 0) {
+                            // Only add it to subpaths
+                            $courses[$k]->managers[] = $ra;
+                        }
+                    }
+                }
+            } else { // course-level
+                if (!array_key_exists($ra->instanceid, $courses)) {
+                    //this course is not in a list, probably a frontpage course
+                    continue;
+                }
+                $courses[$ra->instanceid]->managers[] = $ra;
+            }
+        }
+        $rs->close();
+    }
+
+    return $courses;
+}
+
+/**
  * A list of courses that match a search
  *
  * @global object
@@ -706,7 +835,7 @@ function get_courses_page($categoryid="all", $sort="c.sortorder ASC", $fields="c
  * @param int $totalcount Passed in by reference.
  * @return object {@link $COURSE} records
  */
-function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$totalcount) {
+function get_courses_search($searchterms, $sort='fullname ASC', $page=0, $recordsperpage=50, &$totalcount) {
     global $CFG, $DB;
 
     if ($DB->sql_regex_supported()) {
@@ -722,8 +851,7 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
     if ($DB->get_dbfamily() == 'oracle') {
         $concat = $DB->sql_concat('c.summary', "' '", 'c.fullname', "' '", 'c.idnumber', "' '", 'c.shortname');
     } else {
-        $concat = $DB->sql_concat("COALESCE(c.summary, :empty)", "' '", 'c.fullname', "' '", 'c.idnumber', "' '", 'c.shortname');
-        $params['empty'] = '';
+        $concat = $DB->sql_concat("COALESCE(c.summary, '". $DB->sql_empty() ."')", "' '", 'c.fullname', "' '", 'c.idnumber', "' '", 'c.shortname');
     }
 
     foreach ($searchterms as $searchterm) {
@@ -776,8 +904,7 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
     $limitto   = $limitfrom + $recordsperpage;
 
     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
-    $fields = array_diff(array_keys($DB->get_columns('course')), array('modinfo', 'sectioncache'));
-    $sql = "SELECT c.".join(',c.',$fields)." $ccselect
+    $sql = "SELECT c.* $ccselect
               FROM {course} c
            $ccjoin
              WHERE $searchcond AND c.id <> ".SITEID."
@@ -785,21 +912,17 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
 
     $rs = $DB->get_recordset_sql($sql, $params);
     foreach($rs as $course) {
-        if (!$course->visible) {
-            // preload contexts only for hidden courses or courses we need to return
-            context_instance_preload($course);
-            $coursecontext = context_course::instance($course->id);
-            if (!has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
-                continue;
+        context_instance_preload($course);
+        $coursecontext = context_course::instance($course->id);
+        if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+            // Don't exit this loop till the end
+            // we need to count all the visible courses
+            // to update $totalcount
+            if ($c >= $limitfrom && $c < $limitto) {
+                $courses[$course->id] = $course;
             }
+            $c++;
         }
-        // Don't exit this loop till the end
-        // we need to count all the visible courses
-        // to update $totalcount
-        if ($c >= $limitfrom && $c < $limitto) {
-            $courses[$course->id] = $course;
-        }
-        $c++;
     }
     $rs->close();
 
@@ -809,6 +932,140 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total
     return $courses;
 }
 
+
+/**
+ * Returns a sorted list of categories. Each category object has a context
+ * property that is a context object.
+ *
+ * When asking for $parent='none' it will return all the categories, regardless
+ * of depth. Wheen asking for a specific parent, the default is to return
+ * a "shallow" resultset. Pass false to $shallow and it will return all
+ * the child categories as well.
+ *
+ * @global object
+ * @uses CONTEXT_COURSECAT
+ * @param string $parent The parent category if any
+ * @param string $sort the sortorder
+ * @param bool   $shallow - set to false to get the children too
+ * @return array of categories
+ */
+function get_categories($parent='none', $sort=NULL, $shallow=true) {
+    global $DB;
+
+    if ($sort === NULL) {
+        $sort = 'ORDER BY cc.sortorder ASC';
+    } elseif ($sort ==='') {
+        // leave it as empty
+    } else {
+        $sort = "ORDER BY $sort";
+    }
+
+    list($ccselect, $ccjoin) = context_instance_preload_sql('cc.id', CONTEXT_COURSECAT, 'ctx');
+
+    if ($parent === 'none') {
+        $sql = "SELECT cc.* $ccselect
+                  FROM {course_categories} cc
+               $ccjoin
+                $sort";
+        $params = array();
+
+    } elseif ($shallow) {
+        $sql = "SELECT cc.* $ccselect
+                  FROM {course_categories} cc
+               $ccjoin
+                 WHERE cc.parent=?
+                $sort";
+        $params = array($parent);
+
+    } else {
+        $sql = "SELECT cc.* $ccselect
+                  FROM {course_categories} cc
+               $ccjoin
+                  JOIN {course_categories} ccp
+                       ON ((cc.parent = ccp.id) OR (cc.path LIKE ".$DB->sql_concat('ccp.path',"'/%'")."))
+                 WHERE ccp.id=?
+                $sort";
+        $params = array($parent);
+    }
+    $categories = array();
+
+    $rs = $DB->get_recordset_sql($sql, $params);
+    foreach($rs as $cat) {
+        context_instance_preload($cat);
+        $catcontext = context_coursecat::instance($cat->id);
+        if ($cat->visible || has_capability('moodle/category:viewhiddencategories', $catcontext)) {
+            $categories[$cat->id] = $cat;
+        }
+    }
+    $rs->close();
+    return $categories;
+}
+
+
+/**
+ * Returns an array of category ids of all the subcategories for a given
+ * category.
+ *
+ * @global object
+ * @param int $catid - The id of the category whose subcategories we want to find.
+ * @return array of category ids.
+ */
+function get_all_subcategories($catid) {
+    global $DB;
+
+    $subcats = array();
+
+    if ($categories = $DB->get_records('course_categories', array('parent'=>$catid))) {
+        foreach ($categories as $cat) {
+            array_push($subcats, $cat->id);
+            $subcats = array_merge($subcats, get_all_subcategories($cat->id));
+        }
+    }
+    return $subcats;
+}
+
+/**
+ * Return specified category, default if given does not exist
+ *
+ * @global object
+ * @uses MAX_COURSES_IN_CATEGORY
+ * @uses CONTEXT_COURSECAT
+ * @uses SYSCONTEXTID
+ * @param int $catid course category id
+ * @return object caregory
+ */
+function get_course_category($catid=0) {
+    global $DB;
+
+    $category = false;
+
+    if (!empty($catid)) {
+        $category = $DB->get_record('course_categories', array('id'=>$catid));
+    }
+
+    if (!$category) {
+        // the first category is considered default for now
+        if ($category = $DB->get_records('course_categories', null, 'sortorder', '*', 0, 1)) {
+            $category = reset($category);
+
+        } else {
+            $cat = new stdClass();
+            $cat->name         = get_string('miscellaneous');
+            $cat->depth        = 1;
+            $cat->sortorder    = MAX_COURSES_IN_CATEGORY;
+            $cat->timemodified = time();
+            $catid = $DB->insert_record('course_categories', $cat);
+            // make sure category context exists
+            context_coursecat::instance($catid);
+            mark_context_dirty('/'.SYSCONTEXTID);
+            fix_course_sortorder(); // Required to build course_categories.depth and .path.
+            $category = $DB->get_record('course_categories', array('id'=>$catid));
+        }
+    }
+
+    return $category;
+}
+
 /**
  * Fixes course category and course sortorder, also verifies category and course parents and paths.
  * (circular references are not fixed)
@@ -826,14 +1083,9 @@ function fix_course_sortorder() {
 
     //WARNING: this is PHP5 only code!
 
-    // if there are any changes made to courses or categories we will trigger
-    // the cache events to purge all cached courses/categories data
-    $cacheevents = array();
-
     if ($unsorted = $DB->get_records('course_categories', array('sortorder'=>0))) {
         //move all categories that are not sorted yet to the end
         $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('sortorder'=>0));
-        $cacheevents['changesincoursecat'] = true;
     }
 
     $allcats = $DB->get_records('course_categories', null, 'sortorder, id', 'id, sortorder, parent, depth, path');
@@ -873,9 +1125,7 @@ function fix_course_sortorder() {
     // now walk recursively the tree and fix any problems found
     $sortorder = 0;
     $fixcontexts = array();
-    if (_fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts)) {
-        $cacheevents['changesincoursecat'] = true;
-    }
+    _fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts);
 
     // detect if there are "multiple" frontpage courses and fix them if needed
     $frontcourses = $DB->get_records('course', array('category'=>0), 'id');
@@ -891,7 +1141,6 @@ function fix_course_sortorder() {
             $DB->set_field('course', 'category', $defaultcat->id, array('id'=>$course->id));
             $context = context_course::instance($course->id);
             $fixcontexts[$context->id] = $context;
-            $cacheevents['changesincourse'] = true;
         }
         unset($frontcourses);
     } else {
@@ -905,8 +1154,6 @@ function fix_course_sortorder() {
         }
         context_helper::build_all_paths(false);
         unset($fixcontexts);
-        $cacheevents['changesincourse'] = true;
-        $cacheevents['changesincoursecat'] = true;
     }
 
     // release memory
@@ -917,7 +1164,6 @@ function fix_course_sortorder() {
     // fix frontpage course sortorder
     if ($frontcourse->sortorder != 1) {
         $DB->set_field('course', 'sortorder', 1, array('id'=>$frontcourse->id));
-        $cacheevents['changesincourse'] = true;
     }
 
     // now fix the course counts in category records if needed
@@ -942,7 +1188,6 @@ function fix_course_sortorder() {
             $str = implode(', ', $categories);
             debugging("The number of courses (category id: $str) has reached MAX_COURSES_IN_CATEGORY (" . MAX_COURSES_IN_CATEGORY . "), it will cause a sorting performance issue, please increase the value of MAX_COURSES_IN_CATEGORY in lib/datalib.php file. See tracker issue: MDL-25669", DEBUG_DEVELOPER);
         }
-        $cacheevents['changesincoursecat'] = true;
     }
 
     // now make sure that sortorders in course table are withing the category sortorder ranges
@@ -959,7 +1204,6 @@ function fix_course_sortorder() {
                      WHERE category = ?";
             $DB->execute($sql, array($cat->sortorder, $cat->id));
         }
-        $cacheevents['changesincoursecat'] = true;
     }
     unset($fixcategories);
 
@@ -993,7 +1237,6 @@ function fix_course_sortorder() {
             // it needs full resorting
             $fixcategories[$cat->id] = $cat;
         }
-        $cacheevents['changesincourse'] = true;
     }
     unset($gapcategories);
 
@@ -1005,16 +1248,10 @@ function fix_course_sortorder() {
             if ($course->sortorder != $cat->sortorder + $i) {
                 $course->sortorder = $cat->sortorder + $i;
                 $DB->update_record_raw('course', $course, true);
-                $cacheevents['changesincourse'] = true;
             }
             $i++;
         }
     }
-
-    // advise all caches that need to be rebuilt
-    foreach (array_keys($cacheevents) as $event) {
-        cache_helper::purge_by_event($event);
-    }
 }
 
 /**
@@ -1031,13 +1268,12 @@ function fix_course_sortorder() {
  * @param int $depth
  * @param string $path
  * @param array $fixcontexts
- * @return bool if changes were made
+ * @return void
  */
 function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixcontexts) {
     global $DB;
 
     $depth++;
-    $changesmade = false;
 
     foreach ($children as $cat) {
         $sortorder = $sortorder + MAX_COURSES_IN_CATEGORY;
@@ -1058,15 +1294,11 @@ function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixc
         }
         if ($update) {
             $DB->update_record('course_categories', $cat, true);
-            $changesmade = true;
         }
         if (isset($cat->children)) {
-            if (_fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts)) {
-                $changesmade = true;
-            }
+            _fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts);
         }
     }
-    return $changesmade;
 }
 
 /**
diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php
index c7753a5..6ffbcbe 100644
--- a/lib/dml/moodle_database.php
+++ b/lib/dml/moodle_database.php
@@ -408,7 +408,6 @@ abstract class moodle_database {
             // free memory
             $this->last_sql    = null;
             $this->last_params = null;
-            $this->print_debug_time();
             return;
         }
 
@@ -416,6 +415,7 @@ abstract class moodle_database {
         $type   = $this->last_type;
         $sql    = $this->last_sql;
         $params = $this->last_params;
+        $time   = microtime(true) - $this->last_time;
         $error  = $this->get_last_error();
 
         $this->query_log($error);
@@ -521,25 +521,6 @@ abstract class moodle_database {
     }
 
     /**
-     * Prints the time a query took to run.
-     * @return void
-     */
-    protected function print_debug_time() {
-        if (!$this->get_debug()) {
-            return;
-        }
-        $time = microtime(true) - $this->last_time;
-        $message = "Query took: {$time} seconds.\n";
-        if (CLI_SCRIPT) {
-            echo $message;
-            echo "--------------------------------\n";
-        } else {
-            echo s($message);
-            echo "<hr />\n";
-        }
-    }
-
-    /**
      * Returns the SQL WHERE conditions.
      * @param string $table The table name that these conditions will be validated against.
      * @param array $conditions The conditions to build the where clause. (must not contain numeric indexes)
@@ -2012,14 +1993,12 @@ abstract class moodle_database {
     }
 
     /**
-     * This used to return empty string replacement character.
-     *
-     * @deprecated use bound parameter with empty string instead
-     *
+     * Returns the empty string char used by every supported DB. To be used when
+     * we are searching for that values in our queries. Only Oracle uses this
+     * for now (will be out, once we migrate to proper NULLs if that days arrives)
      * @return string An empty string.
      */
     function sql_empty() {
-        debugging("sql_empty() is deprecated, please use empty string '' as sql parameter value instead", DEBUG_DEVELOPER);
         return '';
     }
 
@@ -2038,13 +2017,9 @@ abstract class moodle_database {
      *
      *     ... AND fieldname = '';
      *
-     * are being used. Final result for text fields should be:
-     *
-     *     ... AND ' . sql_isempty('tablename', 'fieldname', true/false, true);
-     *
-     * and for varchar fields result should be:
+     * are being used. Final result should be:
      *
-     *    ... AND fieldname = :empty; "; $params['empty'] = '';
+     *     ... AND ' . sql_isempty('tablename', 'fieldname', true/false, true/false);
      *
      * (see parameters description below)
      *
@@ -2072,14 +2047,10 @@ abstract class moodle_database {
      *
      *     ... AND fieldname != '';
      *
-     * are being used. Final result for text fields should be:
+     * are being used. Final result should be:
      *
      *     ... AND ' . sql_isnotempty('tablename', 'fieldname', true/false, true/false);
      *
-     * and for varchar fields result should be:
-     *
-     *    ... AND fieldname != :empty; "; $params['empty'] = '';
-     *
      * (see parameters description below)
      *
      * @param string $tablename Name of the table (without prefix). This is not used for now but can be
diff --git a/lib/enrollib.php b/lib/enrollib.php
index e881c9b..98c5e26 100644
--- a/lib/enrollib.php
+++ b/lib/enrollib.php
@@ -516,8 +516,10 @@ function enrol_add_course_navigation(navigation_node $coursenode, $course) {
  * @param string $sort
  * @param int $limit max number of courses
  * @return array
+ * @param int $limitfrom return a subset of records, starting at this point (optional).
+ * @param int $limitnum return a subset comprising this many records in total (optional, required if $limitfrom is set).
  */
-function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
+function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC',$limitfrom=0, $limitnum=0) {
     global $DB, $USER;
 
     // Guest account does not have any courses
@@ -592,7 +594,7 @@ function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder AS
     $params['now1']    = round(time(), -2); // improves db caching
     $params['now2']    = $params['now1'];
 
-    $courses = $DB->get_records_sql($sql, $params, 0, $limit);
+    $courses = $DB->get_records_sql($sql, $params, 0, $limitfrom,$limitnum);
 
     // preload contexts and check visibility
     foreach ($courses as $id=>$course) {
@@ -684,10 +686,10 @@ function enrol_get_course_description_texts($course) {
  * @param string $sort
  * @return array
  */
-function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
+function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC',$limitfrom=0, $limitnum=0) {
     global $DB;
 
-    $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
+    $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort,$limitfrom,$limitnum);
 
     // preload contexts and check visibility
     if ($onlyactive) {
@@ -772,7 +774,7 @@ function enrol_user_sees_own_courses($user = null) {
  * @param string $sort
  * @return array
  */
-function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
+function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC',$limitfrom=0, $limitnum=0) {
     global $DB;
 
     // Guest account does not have any courses
@@ -1545,7 +1547,7 @@ abstract class enrol_plugin {
             return NULL;
         }
 
-        return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));
+        return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));;
     }
 
     /**
@@ -1821,125 +1823,6 @@ abstract class enrol_plugin {
     }
 
     /**
-     * Do any enrolments need expiration processing.
-     *
-     * Plugins that want to call this functionality must implement 'expiredaction' config setting.
-     *
-     * @param progress_trace $trace
-     * @param int $courseid one course, empty mean all
-     * @return bool true if any data processed, false if not
-     */
-    public function process_expirations(progress_trace $trace, $courseid = null) {
-        global $DB;
-
-        $name = $this->get_name();
-        if (!enrol_is_enabled($name)) {
-            $trace->finished();
-            return false;
-        }
-
-        $processed = false;
-        $params = array();
-        $coursesql = "";
-        if ($courseid) {
-            $coursesql = "AND e.courseid = :courseid";
-        }
-
-        // Deal with expired accounts.
-        $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
-
-        if ($action == ENROL_EXT_REMOVED_UNENROL) {
-            $instances = array();
-            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
-                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
-                     WHERE ue.timeend > 0 AND ue.timeend < :now $coursesql";
-            $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'enrol'=>$name, 'courseid'=>$courseid);
-
-            $rs = $DB->get_recordset_sql($sql, $params);
-            foreach ($rs as $ue) {
-                if (!$processed) {
-                    $trace->output("Starting processing of enrol_$name expirations...");
-                    $processed = true;
-                }
-                if (empty($instances[$ue->enrolid])) {
-                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
-                }
-                $instance = $instances[$ue->enrolid];
-                if (!$this->roles_protected()) {
-                    // Let's just guess what extra roles are supposed to be removed.
-                    if ($instance->roleid) {
-                        role_unassign($instance->roleid, $ue->userid, $ue->contextid);
-                    }
-                }
-                // The unenrol cleans up all subcontexts if this is the only course enrolment for this user.
-                $this->unenrol_user($instance, $ue->userid);
-                $trace->output("Unenrolling expired user $ue->userid from course $instance->courseid", 1);
-            }
-            $rs->close();
-            unset($instances);
-
-        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES or $action == ENROL_EXT_REMOVED_SUSPEND) {
-            $instances = array();
-            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
-                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
-                     WHERE ue.timeend > 0 AND ue.timeend < :now
-                           AND ue.status = :useractive $coursesql";
-            $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'useractive'=>ENROL_USER_ACTIVE, 'enrol'=>$name, 'courseid'=>$courseid);
-            $rs = $DB->get_recordset_sql($sql, $params);
-            foreach ($rs as $ue) {
-                if (!$processed) {
-                    $trace->output("Starting processing of enrol_$name expirations...");
-                    $processed = true;
-                }
-                if (empty($instances[$ue->enrolid])) {
-                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
-                }
-                $instance = $instances[$ue->enrolid];
-
-                if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
-                    if (!$this->roles_protected()) {
-                        // Let's just guess what roles should be removed.
-                        $count = $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid));
-                        if ($count == 1) {
-                            role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0));
-
-                        } else if ($count > 1 and $instance->roleid) {
-                            role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
-                        }
-                    }
-                    // In any case remove all roles that belong to this instance and user.
-                    role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id), true);
-                    // Final cleanup of subcontexts if there are no more course roles.
-                    if (0 == $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid))) {
-                        role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
-                    }
-                }
-
-                $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
-                $trace->output("Suspending expired user $ue->userid in course $instance->courseid", 1);
-            }
-            $rs->close();
-            unset($instances);
-
-        } else {
-            // ENROL_EXT_REMOVED_KEEP means no changes.
-        }
-
-        if ($processed) {
-            $trace->output("...finished processing of enrol_$name expirations");
-        } else {
-            $trace->output("No expired enrol_$name enrolments detected");
-        }
-        $trace->finished();
-
-        return $processed;
-    }
-
-    /**
      * Send expiry notifications.
      *
      * Plugin that wants to have expiry notification MUST implement following:
@@ -1951,52 +1834,44 @@ abstract class enrol_plugin {
      * - upgrade code that sets default thresholds for existing courses (should be 1 day),
      * - something that calls this method, such as cron.
      *
-     * @param progress_trace $trace (accepts bool for backwards compatibility only)
+     * @param bool $verbose verbose CLI output
      */
-    public function send_expiry_notifications($trace) {
+    public function send_expiry_notifications($verbose = false) {
         global $DB, $CFG;
 
-        $name = $this->get_name();
-        if (!enrol_is_enabled($name)) {
-            $trace->finished();
-            return;
-        }
-
         // Unfortunately this may take a long time, it should not be interrupted,
         // otherwise users get duplicate notification.
 
         @set_time_limit(0);
         raise_memory_limit(MEMORY_HUGE);
 
+        $name = $this->get_name();
 
         $expirynotifylast = $this->get_config('expirynotifylast', 0);
         $expirynotifyhour = $this->get_config('expirynotifyhour');
         if (is_null($expirynotifyhour)) {
             debugging("send_expiry_notifications() in $name enrolment plugin needs expirynotifyhour setting");
-            $trace->finished();
             return;
         }
 
-        if (!($trace instanceof progress_trace)) {
-            $trace = $trace ? new text_progress_trace() : new null_progress_trace();
-            debugging('enrol_plugin::send_expiry_notifications() now expects progress_trace instance as parameter!', DEBUG_DEVELOPER);
-        }
-
         $timenow = time();
         $notifytime = usergetmidnight($timenow, $CFG->timezone) + ($expirynotifyhour * 3600);
 
         if ($expirynotifylast > $notifytime) {
-            $trace->output($name.' enrolment expiry notifications were already sent today at '.userdate($expirynotifylast, '', $CFG->timezone).'.');
-            $trace->finished();
+            if ($verbose) {
+                mtrace($name.' enrolment expiry notifications were already sent today at '.userdate($expirynotifylast, '', $CFG->timezone).'.');
+            }
             return;
-
         } else if ($timenow < $notifytime) {
-            $trace->output($name.' enrolment expiry notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
-            $trace->finished();
+            if ($verbose) {
+                mtrace($name.' enrolment expiry notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
+            }
             return;
         }
 
-        $trace->output('Processing '.$name.' enrolment expiration notifications...');
+        if ($verbose) {
+            mtrace('Processing '.$name.' enrolment expiration notifications...');
+        }
 
         // Notify users responsible for enrolment once every day.
         $sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
@@ -2015,7 +1890,7 @@ abstract class enrol_plugin {
 
         foreach($rs as $ue) {
             if ($lastenrollid and $lastenrollid != $ue->enrolid) {
-                $this->notify_expiry_enroller($lastenrollid, $users, $trace);
+                $this->notify_expiry_enroller($lastenrollid, $users, $verbose);
                 $users = array();
             }
             $lastenrollid = $ue->enrolid;
@@ -2033,21 +1908,23 @@ abstract class enrol_plugin {
 
             if ($ue->timeend - $ue->expirythreshold + 86400 < $timenow) {
                 // Notify enrolled users only once at the start of the threshold.
-                $trace->output("user $ue->userid was already notified that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
+                if ($verbose) {
+                    mtrace("  user $ue->userid was already notified that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
+                }
                 continue;
             }
 
-            $this->notify_expiry_enrolled($user, $ue, $trace);
+            $this->notify_expiry_enrolled($user, $ue, $verbose);
         }
         $rs->close();
 
         if ($lastenrollid and $users) {
-            $this->notify_expiry_enroller($lastenrollid, $users, $trace);
+            $this->notify_expiry_enroller($lastenrollid, $users, $verbose);
         }
 
-        $trace->output('...notification processing finished.');
-        $trace->finished();
-
+        if ($verbose) {
+            mtrace('...notification processing finished.');
+        }
         $this->set_config('expirynotifylast', $timenow);
     }
 
@@ -2072,9 +1949,9 @@ abstract class enrol_plugin {
      *
      * @param stdClass $user
      * @param stdClass $ue
-     * @param progress_trace $trace
+     * @param bool $verbose
      */
-    protected function notify_expiry_enrolled($user, $ue, progress_trace $trace) {
+    protected function notify_expiry_enrolled($user, $ue, $verbose) {
         global $CFG, $SESSION;
 
         $name = $this->get_name();
@@ -2113,9 +1990,13 @@ abstract class enrol_plugin {
         $message->contexturl        = (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid));
 
         if (message_send($message)) {
-            $trace->output("notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
+            if ($verbose) {
+                mtrace("  notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
+            }
         } else {
-            $trace->output("error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
+            if ($verbose) {
+                mtrace("  error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
+            }
         }
 
         if ($SESSION->lang !== $sessionlang) {
@@ -2133,9 +2014,9 @@ abstract class enrol_plugin {
      *
      * @param int $eid
      * @param array $users
-     * @param progress_trace $trace
+     * @param bool $verbose
      */
-    protected function notify_expiry_enroller($eid, $users, progress_trace $trace) {
+    protected function notify_expiry_enroller($eid, $users, $verbose) {
         global $DB, $SESSION;
 
         $name = $this->get_name();
@@ -2182,9 +2063,13 @@ abstract class enrol_plugin {
         $message->contexturl        = $a->extendurl;
 
         if (message_send($message)) {
-            $trace->output("notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
+            if ($verbose) {
+                mtrace("  notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid");
+            }
         } else {
-            $trace->output("error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
+            if ($verbose) {
+                mtrace("  error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid");
+            }
         }
 
         if ($SESSION->lang !== $sessionlang) {
