MDL-29602 accesslib improvements
authorPetr Skoda <commits@skodak.org>
Fri, 14 Oct 2011 10:48:00 +0000 (12:48 +0200)
committerPetr Skoda <commits@skodak.org>
Sun, 16 Oct 2011 12:05:18 +0000 (14:05 +0200)
Refactoring and improvements of the accesslib.php library including prevention of access for not-logged-in users when forcelogin enabled, improved context caching, OOP refactoring of contexts, fixed context loading, deduplication of role definitions in user sessions, installation improvements, decoupling of enrolment checking from capability loading, added detection of deleted and non-existent users in has_capability(), new function accesslib test, auth and enrol upgrade notes.

More details are available in tracker subtasks.

26 files changed:
admin/report/questioninstances/index.php
admin/roles/usersroles.php
admin/tool/capability/index.php
auth/shibboleth/index.php
auth/upgrade.txt [new file with mode: 0644]
blog/edit.php
blog/edit_form.php
blog/locallib.php
enrol/guest/lib.php
enrol/paypal/return.php
enrol/upgrade.txt [new file with mode: 0644]
lib/accesslib.php
lib/datalib.php
lib/db/install.php
lib/deprecatedlib.php
lib/enrollib.php
lib/moodlelib.php
lib/navigationlib.php
lib/sessionlib.php
lib/simpletest/fulltestaccesslib.php [new file with mode: 0644]
lib/simpletest/testaccesslib.php [deleted file]
lib/upgradelib.php
login/token.php
rss/file.php
webservice/lib.php
webservice/upload.php

index 2add057..3719baf 100644 (file)
@@ -65,13 +65,15 @@ if ($requestedqtype) {
 
     // Get the question counts, and all the context information, for each
     // context. That is, rows of these results can be used as $context objects.
+    $ctxpreload = context_helper::get_preload_record_columns_sql('con');
+    $ctxgroupby = implode(',', array_keys(context_helper::get_preload_record_columns('con')));
     $counts = $DB->get_records_sql("
-            SELECT qc.contextid, count(1) as numquestions, sum(hidden) as numhidden, con.id, con.contextlevel, con.instanceid, con.path, con.depth
+            SELECT qc.contextid, count(1) as numquestions, sum(hidden) as numhidden, $ctxpreload
             FROM {question} q
             JOIN {question_categories} qc ON q.category = qc.id
             JOIN {context} con ON con.id = qc.contextid
             $sqlqtypetest
-            GROUP BY contextid, con.id, con.contextlevel, con.instanceid, con.path, con.depth
+            GROUP BY qc.contextid, $ctxgroupby
             ORDER BY numquestions DESC, numhidden ASC, con.contextlevel ASC, con.id ASC", $params);
 
     // Print the report heading.
@@ -94,8 +96,10 @@ if ($requestedqtype) {
     $totalhidden = 0;
     foreach ($counts as $count) {
         // Work out a link for editing questions in this context.
-        $contextname = print_context_name($count);
-        $url = question_edit_url($count);
+        context_helper::preload_from_record($count);
+        $context = context::instance_by_id($count->contextid);
+        $contextname = $context->get_context_name();
+        $url = question_edit_url($context);
         if ($url) {
             $contextname = '<a href="' . $url . '" title="' .
                     get_string('editquestionshere', 'report_questioninstances') .
index 67c9ef2..0c4b157 100644 (file)
@@ -96,7 +96,8 @@ foreach ($contexts as $conid => $con) {
 
 /// Put the contexts into a tree structure.
 foreach ($contexts as $conid => $con) {
-    $parentcontextid = get_parent_contextid($con);
+    $context = context::instance_by_id($conid);
+    $parentcontextid = get_parent_contextid($context);
     if ($parentcontextid) {
         $contexts[$parentcontextid]->children[] = $conid;
     }
@@ -156,13 +157,13 @@ function print_report_tree($contextid, $contexts, $systemcontext, $fullname) {
     }
 
     // Pull the current context into an array for convinience.
-    $context = $contexts[$contextid];
+    $context = context::instance_by_id($contextid);
 
     // Print the context name.
-    echo $OUTPUT->heading(print_context_name($contexts[$contextid]), 4, 'contextname');
+    echo $OUTPUT->heading($context->get_context_name(), 4, 'contextname');
 
     // If there are any role assignments here, print them.
-    foreach ($context->roleassignments as $ra) {
+    foreach ($contexts[$contextid]->roleassignments as $ra) {
         $value = $ra->contextid . ',' . $ra->roleid;
         $inputid = 'unassign' . $value;
 
index 43fd89e..454caa0 100644 (file)
@@ -138,7 +138,8 @@ if ($capability) {
 
     // Put the contexts into a tree structure.
     foreach ($contexts as $conid => $con) {
-        $parentcontextid = get_parent_contextid($con);
+        $context = context::instance_by_id($conid);
+        $parentcontextid = get_parent_contextid($context);
         if ($parentcontextid) {
             $contexts[$parentcontextid]->children[] = $conid;
         }
@@ -196,7 +197,8 @@ function print_report_tree($contextid, $contexts, $allroles) {
         $url = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$contextid";
         $title = get_string('changeoverrides', 'tool_capability');
     }
-    echo '<h3><a href="' . $url . '" title="' . $title . '">', print_context_name($contexts[$contextid]), '</a></h3>';
+    $context = context::instance_by_id($contextid);
+    echo '<h3><a href="' . $url . '" title="' . $title . '">', $context->get_context_name(), '</a></h3>';
 
     // If there are any role overrides here, print them.
     if (!empty($contexts[$contextid]->rolecapabilities)) {
index ea33b6c..7bc8f90 100644 (file)
@@ -39,7 +39,9 @@
 
         if ($shibbolethauth->user_login($frm->username, $frm->password)) {
 
-            $USER = authenticate_user_login($frm->username, $frm->password);
+            $user = authenticate_user_login($frm->username, $frm->password);
+            enrol_check_plugins($user);
+            session_set_user($user);
 
             $USER->loggedin = true;
             $USER->site     = $CFG->wwwroot; // for added security, store the site in the
@@ -75,9 +77,6 @@
                 }
             }
 
-            enrol_check_plugins($USER);
-            load_all_capabilities();     /// This is what lets the user do anything on the site  :-)
-
             redirect($urltogo);
 
             exit;
diff --git a/auth/upgrade.txt b/auth/upgrade.txt
new file mode 100644 (file)
index 0000000..d3e1761
--- /dev/null
@@ -0,0 +1,10 @@
+This files describes API changes in /auth/* - plugins,
+information provided here is intended especially for developers.
+
+=== 2.2 ===
+
+required changes in code:
+* the correct sequence to set up global $USER is:
+    $user = get_complete_user_data('username', $username); // or $user = authenticate_user_login()
+    enrol_check_plugins($user);
+    session_set_user($user);
index ba5bdfc..1ba7ac4 100644 (file)
@@ -143,9 +143,9 @@ if (!empty($entry->id)) {
     if ($CFG->useblogassociations && ($blogassociations = $DB->get_records('blog_association', array('blogid' => $entry->id)))) {
 
         foreach ($blogassociations as $assocrec) {
-            $contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
+            $context = get_context_instance_by_id($assocrec->contextid);
 
-            switch ($contextrec->contextlevel) {
+            switch ($context->contextlevel) {
                 case CONTEXT_COURSE:
                     $entry->courseassoc = $assocrec->contextid;
                     break;
index cbfd4ae..04c67e5 100644 (file)
@@ -95,7 +95,7 @@ class blog_edit_form extends moodleform {
                     $a->modname = $mod->name;
                     $context = get_context_instance(CONTEXT_MODULE, $modid);
                 } else {
-                    $context = $DB->get_record('context', array('id' => $entry->modassoc));
+                    $context = get_context_instance_by_id($entry->modassoc);
                     $cm = $DB->get_record('course_modules', array('id' => $context->instanceid));
                     $a = new stdClass();
                     $a->modtype = $DB->get_field('modules', 'name', array('id' => $cm->module));
@@ -134,7 +134,7 @@ class blog_edit_form extends moodleform {
 
         // validate course association
         if (!empty($data['courseassoc']) && has_capability('moodle/blog:associatecourse', $sitecontext)) {
-            $coursecontext = $DB->get_record('context', array('id' => $data['courseassoc'], 'contextlevel' => CONTEXT_COURSE));
+            $coursecontext = get_context_instance(CONTEXT_COURSE, $data['courseassoc']);
 
             if ($coursecontext)  {
                 if (!is_enrolled($coursecontext) and !is_viewing($coursecontext)) {
@@ -149,12 +149,12 @@ class blog_edit_form extends moodleform {
         if (!empty($data['modassoc'])) {
             $modcontextid = $data['modassoc'];
 
-            $modcontext = $DB->get_record('context', array('id' => $modcontextid, 'contextlevel' => CONTEXT_MODULE));
+            $modcontext = get_context_instance(CONTEXT_MODULE, $modcontextid);
 
             if ($modcontext) {
                 // get context of the mod's course
                 $path = explode('/', $modcontext->path);
-                $coursecontext = $DB->get_record('context', array('id' => $path[(count($path) - 2)]));
+                $coursecontext = get_context_instance_by_id($path[(count($path) - 2)]);
 
                 // ensure only one course is associated
                 if (!empty($data['courseassoc'])) {
index 51afba8..19b01ca 100644 (file)
@@ -247,10 +247,10 @@ class blog_entry {
 
             // First find and show the associated course
             foreach ($blogassociations as $assocrec) {
-                $contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
-                if ($contextrec->contextlevel ==  CONTEXT_COURSE) {
-                    $assocurl = new moodle_url('/course/view.php', array('id' => $contextrec->instanceid));
-                    $text = $DB->get_field('course', 'shortname', array('id' => $contextrec->instanceid)); //TODO: performance!!!!
+                $context = get_context_instance_by_id($assocrec->contextid);
+                if ($context->contextlevel ==  CONTEXT_COURSE) {
+                    $assocurl = new moodle_url('/course/view.php', array('id' => $context->instanceid));
+                    $text = $DB->get_field('course', 'shortname', array('id' => $context->instanceid)); //TODO: performance!!!!
                     $assocstr .= $OUTPUT->action_icon($assocurl, new pix_icon('i/course', $text), null, array(), true);
                     $hascourseassocs = true;
                     $assoctype = get_string('course');
@@ -259,15 +259,15 @@ class blog_entry {
 
             // Now show mod association
             foreach ($blogassociations as $assocrec) {
-                $contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
+                $context = get_context_instance_by_id($assocrec->contextid);
 
-                if ($contextrec->contextlevel ==  CONTEXT_MODULE) {
+                if ($context->contextlevel ==  CONTEXT_MODULE) {
                     if ($hascourseassocs) {
                         $assocstr .= ', ';
                         $hascourseassocs = false;
                     }
 
-                    $modinfo = $DB->get_record('course_modules', array('id' => $contextrec->instanceid));
+                    $modinfo = $DB->get_record('course_modules', array('id' => $context->instanceid));
                     $modname = $DB->get_field('modules', 'name', array('id' => $modinfo->module));
 
                     $assocurl = new moodle_url('/mod/'.$modname.'/view.php', array('id' => $modinfo->id));
index 570539c..39516e1 100644 (file)
@@ -77,7 +77,7 @@ class enrol_guest_plugin extends enrol_plugin {
         if (empty($instance->password)) {
             // Temporarily assign them some guest role for this context
             $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
-            $USER->access = load_temp_role($context, $CFG->guestroleid, $USER->access);
+            load_temp_course_role($context, $CFG->guestroleid);
             return ENROL_REQUIRE_LOGIN_CACHE_PERIOD + time();
         }
 
@@ -131,7 +131,7 @@ class enrol_guest_plugin extends enrol_plugin {
 
                 // add guest role
                 $context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
-                $USER->access = load_temp_role($context, $CFG->guestroleid, $USER->access);
+                load_temp_course_role($context, $CFG->guestroleid);
 
                 // go to the originally requested page
                 if (!empty($SESSION->wantsurl)) {
index 89cb2d0..a8a6557 100644 (file)
@@ -38,9 +38,6 @@ $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
 
 require_login();
 
-// Refreshing enrolment data in the USER session
-load_all_capabilities();
-
 if ($SESSION->wantsurl) {
     $destination = $SESSION->wantsurl;
     unset($SESSION->wantsurl);
diff --git a/enrol/upgrade.txt b/enrol/upgrade.txt
new file mode 100644 (file)
index 0000000..4fb4c49
--- /dev/null
@@ -0,0 +1,8 @@
+This files describes API changes in /enrol/* - plugins,
+information provided here is intended especially for developers.
+
+=== 2.2 ===
+
+required changes in code:
+* load_temp_role() is deprecated, use load_temp_course_role() instead, temp role not loaded
+* remove_temp_role() is deprecated, use remove_temp_course_roles() instead
index a2b50e2..21ba4b4 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
  * General users probably only care about
  *
  * Context handling
- * - get_context_instance()
- * - get_context_instance_by_id()
- * - get_parent_contexts()
- * - get_child_contexts()
+ * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid)
+ * - context::instance_by_id($contextid)
+ * - $context->get_parent_contexts();
+ * - $context->get_child_contexts();
  *
  * Whether the user can do something...
  * - has_capability()
  * - has_all_capabilities()
  * - require_capability()
  * - require_login() (from moodlelib)
+ * - is_siteadmin()
  *
  * What courses has this user access to?
- * - get_user_courses_bycap()
+ * - get_enrolled_users()
  *
  * What users can do X in this context?
  * - get_users_by_capability()
  *
- * Enrol/unenrol
- * - enrol_into_course()
- * - role_assign()/role_unassign()
+ * Modify roles
+ * - role_assign()
+ * - role_unassign()
+ * - role_unassign_all()
  *
  *
- * Advanced use
+ * Advanced - for internal use only
  * - load_all_capabilities()
  * - reload_all_capabilities()
  * - has_capability_in_accessdata()
- * - is_siteadmin()
  * - get_user_access_sitewide()
- * - load_subcontext()
- * - get_role_access_bycontext()
+ * - load_course_context()
+ * - load_role_access_by_context()
+ * - etc.
  *
  * <b>Name conventions</b>
  *
@@ -65,7 +66,7 @@
  * which - for the logged-in user, will be in $USER->access
  *
  * For other users can be generated and passed around (but may also be cached
- * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser.
+ * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
  *
  * $accessdata is a multidimensional array, holding
  * role assignments (RAs), role-capabilities-perm sets
  * Things are keyed on "contextpaths" (the path field of
  * the context table) for fast walking up/down the tree.
  * <code>
- * $accessdata[ra][$contextpath]= array($roleid)
- *                [$contextpath]= array($roleid)
- *                [$contextpath]= array($roleid)
+ * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
+ *                  [$contextpath] = array($roleid=>$roleid)
+ *                  [$contextpath] = array($roleid=>$roleid)
  * </code>
  *
  * Role definitions are stored like this
  * (no cap merge is done - so it's compact)
  *
  * <code>
- * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
- *                                        [mod/forum:editallpost] = -1
- *                                        [mod/forum:startdiscussion] = -1000
+ * $accessdata['rdef']["$contextpath:$roleid"]['mod/forum:viewpost'] = 1
+ *                                            ['mod/forum:editallpost'] = -1
+ *                                            ['mod/forum:startdiscussion'] = -1000
  * </code>
  *
- * See how has_capability_in_accessdata() walks up/down the tree.
+ * See how has_capability_in_accessdata() walks up the tree.
  *
- * Normally - specially for the logged-in user, we only load
- * rdef and ra down to the course level, but not below. This
- * keeps accessdata small and compact. Below-the-course ra/rdef
- * are loaded as needed. We keep track of which courses we
- * have loaded ra/rdef in
+ * First we only load rdef and ra down to the course level, but not below.
+ * This keeps accessdata small and compact. Below-the-course ra/rdef
+ * are loaded as needed. We keep track of which courses we have loaded ra/rdef in
  * <code>
- * $accessdata[loaded] = array($contextpath, $contextpath)
+ * $accessdata['loaded'] = array($courseid1=>1, $courseid2=>1)
  * </code>
  *
  * <b>Stale accessdata</b>
 
 defined('MOODLE_INTERNAL') || die();
 
-/** permission definitions */
+/** No capability change */
 define('CAP_INHERIT', 0);
-/** permission definitions */
+/** Allow permission, overrides CAP_PREVENT defined in parent contexts */
 define('CAP_ALLOW', 1);
-/** permission definitions */
+/** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
 define('CAP_PREVENT', -1);
-/** permission definitions */
+/** Prohibit permission, overrides everything in current and child contexts */
 define('CAP_PROHIBIT', -1000);
 
-/** context definitions */
+/** System context level - only one instance in every system */
 define('CONTEXT_SYSTEM', 10);
-/** context definitions */
+/** User context level -  one instance for each user describing what others can do to user */
 define('CONTEXT_USER', 30);
-/** context definitions */
+/** Course category context level - one instance for each category */
 define('CONTEXT_COURSECAT', 40);
-/** context definitions */
+/** Course context level - one instances for each course */
 define('CONTEXT_COURSE', 50);
-/** context definitions */
+/** Course module context level - one instance for each course module */
 define('CONTEXT_MODULE', 70);
-/** context definitions */
+/**
+ * Block context level - one instance for each block, sticky blocks are tricky
+ * because ppl think they should be able to override them at lower contexts.
+ * Any other context level instance can be parent of block context.
+ */
 define('CONTEXT_BLOCK', 80);
 
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_MANAGETRUST', 0x0001);
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_CONFIG',      0x0002);
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** Capability allows user to add scritped content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_XSS',         0x0004);
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_PERSONAL',    0x0008);
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** Capability allows users to add content otehrs may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_SPAM',        0x0010);
-/** capability risks - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
+/** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 define('RISK_DATALOSS',    0x0020);
 
 /** rolename displays - the name as defined in the role definition */
 define('ROLENAME_ORIGINAL', 0);
 /** rolename displays - the name as defined by a role alias */
 define('ROLENAME_ALIAS', 1);
-/** rolename displays - Both, like this:  Role alias (Original)*/
+/** rolename displays - Both, like this:  Role alias (Original) */
 define('ROLENAME_BOTH', 2);
-/** rolename displays - the name as defined in the role definition and the shortname in brackets*/
+/** rolename displays - the name as defined in the role definition and the shortname in brackets */
 define('ROLENAME_ORIGINALANDSHORT', 3);
-/** rolename displays - the name as defined by a role alias, in raw form suitable for editing*/
+/** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
 define('ROLENAME_ALIAS_RAW', 4);
-/** rolename displays - the name is simply short role name*/
+/** rolename displays - the name is simply short role name */
 define('ROLENAME_SHORT', 5);
 
-/**
- * Internal class provides a cache of context information. The cache is
- * restricted in size.
- *
- * This cache should NOT be used outside accesslib.php!
- *
- * @private
- * @author Sam Marshall
- */
-class context_cache {
-    private $contextsbyid;
-    private $contexts;
-    private $count;
-
-    /**
-     * @var int Maximum number of contexts that will be cached.
-     */
-    const MAX_SIZE = 2500;
-    /**
-     * @var int Once contexts reach maximum number, this many will be removed from cache.
-     */
-    const REDUCE_SIZE = 1000;
-
-    /**
-     * Initialises (empty)
-     */
-    public function __construct() {
-        $this->reset();
-    }
-
-    /**
-     * Resets the cache to remove all data.
-     */
-    public function reset() {
-        $this->contexts     = array();
-        $this->contextsbyid = array();
-        $this->count        = 0;
-    }
-
-    /**
-     * Adds a context to the cache. If the cache is full, discards a batch of
-     * older entries.
-     * @param stdClass $context New context to add
-     */
-    public function add(stdClass $context) {
-        if ($this->count >= self::MAX_SIZE) {
-            for ($i=0; $i<self::REDUCE_SIZE; $i++) {
-                if ($first = reset($this->contextsbyid)) {
-                    unset($this->contextsbyid[$first->id]);
-                    unset($this->contexts[$first->contextlevel][$first->instanceid]);
-                }
-            }
-            $this->count -= self::REDUCE_SIZE;
-            if ($this->count < 0) {
-                // most probably caused by the drift, the reset() above
-                // might have returned false because there might not be any more elements
-                $this->count = 0;
-            }
-        }
-
-        $this->contexts[$context->contextlevel][$context->instanceid] = $context;
-        $this->contextsbyid[$context->id] = $context;
-
-        // Note the count may get out of synch slightly if you cache a context
-        // that is already cached, but it doesn't really matter much and I
-        // didn't think it was worth the performance hit.
-        $this->count++;
-    }
-
-    /**
-     * Removes a context from the cache.
-     * @param stdClass $context Context object to remove (must include fields
-     *   ->id, ->contextlevel, ->instanceid at least)
-     */
-    public function remove(stdClass $context) {
-        unset($this->contexts[$context->contextlevel][$context->instanceid]);
-        unset($this->contextsbyid[$context->id]);
-
-        // Again the count may get a bit out of synch if you remove things
-        // that don't exist
-        $this->count--;
-
-        if ($this->count < 0) {
-            $this->count = 0;
-        }
-    }
-
-    /**
-     * Gets a context from the cache.
-     * @param int $contextlevel Context level
-     * @param int $instance Instance ID
-     * @return stdClass|bool Context or false if not in cache
-     */
-    public function get($contextlevel, $instance) {
-        if (isset($this->contexts[$contextlevel][$instance])) {
-            return $this->contexts[$contextlevel][$instance];
-        }
-        return false;
-    }
-
-    /**
-     * Gets a context from the cache based on its id.
-     * @param int $id Context ID
-     * @return stdClass|bool Context or false if not in cache
-     */
-    public function get_by_id($id) {
-        if (isset($this->contextsbyid[$id])) {
-            return $this->contextsbyid[$id];
-        }
-        return false;
-    }
-
-    /**
-     * @return int Count of contexts in cache (approximately)
-     */
-    public function get_approx_count() {
-        return $this->count;
-    }
+/** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
+if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
+    define('CONTEXT_CACHE_MAX_SIZE', 2500);
 }
 
 /**
@@ -302,19 +190,16 @@ class context_cache {
  * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
  * as methods of a class, instead of functions.
  *
+ * @private
  * @global stdClass $ACCESSLIB_PRIVATE
  * @name $ACCESSLIB_PRIVATE
  */
 global $ACCESSLIB_PRIVATE;
 $ACCESSLIB_PRIVATE = new stdClass();
-$ACCESSLIB_PRIVATE->contexcache      = new context_cache();
-$ACCESSLIB_PRIVATE->systemcontext    = null; // Used in get_system_context
-$ACCESSLIB_PRIVATE->dirtycontexts    = null; // Dirty contexts cache
-$ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER
-$ACCESSLIB_PRIVATE->roledefinitions  = array(); // role definitions cache - helps a lot with mem usage in cron
-$ACCESSLIB_PRIVATE->croncache        = array(); // Used in get_role_access
-$ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts.
-$ACCESSLIB_PRIVATE->capabilities     = null; // detailed information about the capabilities
+$ACCESSLIB_PRIVATE->dirtycontexts    = null;    // Dirty contexts cache, loaded from DB once per page
+$ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
+$ACCESSLIB_PRIVATE->rolepermissions  = array(); // role permissions cache - helps a lot with mem usage
+$ACCESSLIB_PRIVATE->capabilities     = null;    // detailed information about the capabilities
 
 /**
  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
@@ -322,88 +207,62 @@ $ACCESSLIB_PRIVATE->capabilities     = null; // detailed information about the c
  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
  * accesslib's private caches. You need to do this before setting up test data,
  * and also at the end of the tests.
+ *
+ * @return void
  */
 function accesslib_clear_all_caches_for_unit_testing() {
-    global $UNITTEST, $USER, $ACCESSLIB_PRIVATE;
+    global $UNITTEST, $USER;
     if (empty($UNITTEST->running)) {
         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
     }
-    $ACCESSLIB_PRIVATE->contexcache      = new context_cache();
-    $ACCESSLIB_PRIVATE->systemcontext    = null;
-    $ACCESSLIB_PRIVATE->dirtycontexts    = null;
-    $ACCESSLIB_PRIVATE->accessdatabyuser = array();
-    $ACCESSLIB_PRIVATE->roledefinitions  = array();
-    $ACCESSLIB_PRIVATE->croncache        = array();
-    $ACCESSLIB_PRIVATE->preloadedcourses = array();
-    $ACCESSLIB_PRIVATE->capabilities     = null;
+
+    accesslib_clear_all_caches(true);
 
     unset($USER->access);
 }
 
 /**
- * This is really slow!!! do not use above course context level
+ * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
  *
- * @param int $roleid
- * @param object $context
- * @return array
+ * This reset does not touch global $USER.
+ *
+ * @private
+ * @param bool $resetcontexts
+ * @return void
  */
-function get_role_context_caps($roleid, $context) {
-    global $DB;
-
-    //this is really slow!!!! - do not use above course context level!
-    $result = array();
-    $result[$context->id] = array();
+function accesslib_clear_all_caches($resetcontexts) {
+    global $ACCESSLIB_PRIVATE;
 
-    // first emulate the parent context capabilities merging into context
-    $searchcontexts = array_reverse(get_parent_contexts($context));
-    array_push($searchcontexts, $context->id);
-    foreach ($searchcontexts as $cid) {
-        if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
-            foreach ($capabilities as $cap) {
-                if (!array_key_exists($cap->capability, $result[$context->id])) {
-                    $result[$context->id][$cap->capability] = 0;
-                }
-                $result[$context->id][$cap->capability] += $cap->permission;
-            }
-        }
-    }
+    $ACCESSLIB_PRIVATE->dirtycontexts    = null;
+    $ACCESSLIB_PRIVATE->accessdatabyuser = array();
+    $ACCESSLIB_PRIVATE->rolepermissions  = array();
+    $ACCESSLIB_PRIVATE->capabilities     = null;
 
-    // now go through the contexts bellow given context
-    $searchcontexts = array_keys(get_child_contexts($context));
-    foreach ($searchcontexts as $cid) {
-        if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
-            foreach ($capabilities as $cap) {
-                if (!array_key_exists($cap->contextid, $result)) {
-                    $result[$cap->contextid] = array();
-                }
-                $result[$cap->contextid][$cap->capability] = $cap->permission;
-            }
-        }
+    if ($resetcontexts) {
+        context_helper::reset_caches();
     }
-
-    return $result;
 }
 
 /**
  * Gets the accessdata for role "sitewide" (system down to course)
  *
+ * @private
  * @param int $roleid
- * @param array $accessdata defaults to null
  * @return array
  */
-function get_role_access($roleid, $accessdata = null) {
-    global $CFG, $DB;
+function get_role_access($roleid) {
+    global $DB, $ACCESSLIB_PRIVATE;
 
-    /* Get it in 1 cheap DB query...
+    /* Get it in 1 DB query...
      * - relevant role caps at the root and down
      *   to the course level - but not below
      */
-    if (is_null($accessdata)) {
-        $accessdata           = array(); // named list
-        $accessdata['ra']     = array();
-        $accessdata['rdef']   = array();
-        $accessdata['loaded'] = array();
-    }
+
+    //TODO: MUC - this could be cached in shared memory to speed up first page loading, web crawlers, etc.
+
+    $accessdata = get_empty_accessdata();
+
+    $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
 
     //
     // Overrides for the role IN ANY CONTEXTS
@@ -412,92 +271,37 @@ function get_role_access($roleid, $accessdata = null) {
     $sql = "SELECT ctx.path,
                    rc.capability, rc.permission
               FROM {context} ctx
-              JOIN {role_capabilities} rc
-                   ON rc.contextid=ctx.id
-             WHERE rc.roleid = ?
-                   AND ctx.contextlevel <= ".CONTEXT_COURSE."
-          ORDER BY ctx.depth, ctx.path";
+              JOIN {role_capabilities} rc ON rc.contextid = ctx.id
+         LEFT JOIN {context} cctx
+                   ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
+             WHERE rc.roleid = ? AND cctx.id IS NULL";
     $params = array($roleid);
 
     // we need extra caching in CLI scripts and cron
-    if (CLI_SCRIPT) {
-        global $ACCESSLIB_PRIVATE;
-
-        if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) {
-            $ACCESSLIB_PRIVATE->croncache[$roleid] = array();
-            $rs = $DB->get_recordset_sql($sql, $params);
-            foreach ($rs as $rd) {
-                $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd;
-            }
-            $rs->close();
-        }
-
-        foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) {
-            $k = "{$rd->path}:{$roleid}";
-            $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
-        }
-
-    } else {
-        $rs = $DB->get_recordset_sql($sql, $params);
-        if ($rs->valid()) {
-            foreach ($rs as $rd) {
-                $k = "{$rd->path}:{$roleid}";
-                $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
-            }
-            unset($rd);
-        }
-        $rs->close();
+    $rs = $DB->get_recordset_sql($sql, $params);
+    foreach ($rs as $rd) {
+        $k = "{$rd->path}:{$roleid}";
+        $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
     }
+    $rs->close();
 
-    return $accessdata;
-}
-
-/**
- * Gets the accessdata for role "sitewide" (system down to course)
- *
- * @param int $roleid
- * @param array $accessdata defaults to null
- * @return array
- */
-function get_default_frontpage_role_access($roleid, $accessdata = null) {
-
-    global $CFG, $DB;
-
-    $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
-    $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
-
-    //
-    // Overrides for the role in any contexts related to the course
-    //
-    $sql = "SELECT ctx.path,
-                   rc.capability, rc.permission
-              FROM {context} ctx
-              JOIN {role_capabilities} rc
-                   ON rc.contextid=ctx.id
-             WHERE rc.roleid = ?
-                   AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?)
-                   AND ctx.contextlevel <= ".CONTEXT_COURSE."
-          ORDER BY ctx.depth, ctx.path";
-    $params = array($roleid, "$base/%");
-
-    $rs = $DB->get_recordset_sql($sql, $params);
-    if ($rs->valid()) {
-        foreach ($rs as $rd) {
-            $k = "{$rd->path}:{$roleid}";
-            $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
+    // share the role definitions
+    foreach ($accessdata['rdef'] as $k=>$unused) {
+        if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
+            $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
         }
-        unset($rd);
+        $accessdata['rdef_count']++;
+        $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
     }
-    $rs->close();
 
     return $accessdata;
 }
 
-
 /**
- * Get the default guest role
+ * Get the default guest role, this is used for guest account,
+ * search engine spiders, etc.
  *
- * @return stdClass role
+ * @return stdClass role record
  */
 function get_guest_role() {
     global $CFG, $DB;
@@ -515,7 +319,7 @@ function get_guest_role() {
         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
             return $guestrole;
         } else {
-            //somebody is messing with guest roles, remove incorrect setting and try to find a new one
+            // somebody is messing with guest roles, remove incorrect setting and try to find a new one
             set_config('guestroleid', '');
             return get_guest_role();
         }
@@ -525,7 +329,7 @@ function get_guest_role() {
 /**
  * Check whether a user has a particular capability in a given context.
  *
- * For example::
+ * For example:
  *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  *      has_capability('mod/forum:replypost',$context)
  *
@@ -536,16 +340,16 @@ function get_guest_role() {
  * or capabilities with XSS, config or data loss risks.
  *
  * @param string $capability the name of the capability to check. For example mod/forum:view
- * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
+ * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
  * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
  * @param boolean $doanything If false, ignores effect of admin role assignment
  * @return boolean true if the user has this capability. Otherwise false.
  */
-function has_capability($capability, $context, $user = null, $doanything = true) {
-    global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
+function has_capability($capability, context $context, $user = null, $doanything = true) {
+    global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
 
     if (during_initial_install()) {
-        if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") {
+        if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cli/install.php" or $SCRIPT === "/$CFG->admin/cli/install_database.php") {
             // we are in an installer - roles can not work yet
             return true;
         } else {
@@ -557,90 +361,64 @@ function has_capability($capability, $context, $user = null, $doanything = true)
         throw new coding_exception('Legacy capabilities can not be used any more!');
     }
 
-    // the original $CONTEXT here was hiding serious errors
-    // for security reasons do not reuse previous context
-    if (empty($context)) {
-        debugging('Incorrect context specified');
+    if (!is_bool($doanything)) {
+        throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
+    }
+
+    // capability must exist
+    if (!$capinfo = get_capability_info($capability)) {
+        debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
         return false;
     }
-    if (!is_bool($doanything)) {
-        throw new coding_exception('Capability parameter "doanything" is wierd ("'.$doanything.'"). This has to be fixed in code.');
+
+    if (!isset($USER->id)) {
+        // should never happen
+        $USER->id = 0;
     }
 
     // make sure there is a real user specified
     if ($user === null) {
-        $userid = isset($USER->id) ? $USER->id : 0;
+        $userid = $USER->id;
     } else {
         $userid = is_object($user) ? $user->id : $user;
     }
 
-    // capability must exist
-    if (!$capinfo = get_capability_info($capability)) {
-        debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
+    // make sure forcelogin cuts off not-logged-in users if enabled
+    if (!empty($CFG->forcelogin) and $userid == 0) {
         return false;
     }
+
     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
-    if (($capinfo->captype === 'write') or ((int)$capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
+    if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
         if (isguestuser($userid) or $userid == 0) {
             return false;
         }
     }
 
-    if (is_null($context->path) or $context->depth == 0) {
-        //this should not happen
-        $contexts = array(SYSCONTEXTID, $context->id);
-        $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
-        debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
-
-    } else {
-        $contexts = explode('/', $context->path);
-        array_shift($contexts);
-    }
-
-    if (CLI_SCRIPT && !isset($USER->access)) {
-        // In cron, some modules setup a 'fake' $USER,
-        // ensure we load the appropriate accessdata.
-        if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
-            $ACCESSLIB_PRIVATE->dirtycontexts = null; //load fresh dirty contexts
+    // somehow make sure the user is not deleted and actually exists
+    if ($userid != 0) {
+        if ($userid == $USER->id and isset($USER->deleted)) {
+            // this prevents one query per page, it is a bit of cheating,
+            // but hopefully session is terminated properly once user is deleted
+            if ($USER->deleted) {
+                return false;
+            }
         } else {
-            load_user_accessdata($userid);
-            $ACCESSLIB_PRIVATE->dirtycontexts = array();
-        }
-        $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
-
-    } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) {
-        // caps not loaded yet - better to load them to keep BC with 1.8
-        // not-logged-in user or $USER object set up manually first time here
-        load_all_capabilities();
-        $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now
-        $ACCESSLIB_PRIVATE->roledefinitions = array();
-    }
-
-    // Load dirty contexts list if needed
-    if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
-        if (isset($USER->access['time'])) {
-            $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
-        }
-        else {
-            $ACCESSLIB_PRIVATE->dirtycontexts = array();
+            if (!context_user::instance($userid, IGNORE_MISSING)) {
+                // no user context == invalid userid
+                return false;
+            }
         }
     }
 
-    // Careful check for staleness...
-    if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
-        // reload all capabilities - preserving loginas, roleswitches, etc
-        // and then cleanup any marks of dirtyness... at least from our short
-        // term memory! :-)
-        $ACCESSLIB_PRIVATE->accessdatabyuser = array();
-        $ACCESSLIB_PRIVATE->roledefinitions = array();
-
-        if (CLI_SCRIPT) {
-            load_user_accessdata($userid);
-            $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
-            $ACCESSLIB_PRIVATE->dirtycontexts = array();
-
+    // context path/depth must be valid
+    if (empty($context->path) or $context->depth == 0) {
+        // this should not happen often, each upgrade tries to rebuild the context paths
+        debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()');
+        if (is_siteadmin($userid)) {
+            return true;
         } else {
-            reload_all_capabilities();
+            return false;
         }
     }
 
@@ -672,49 +450,29 @@ function has_capability($capability, $context, $user = null, $doanything = true)
         }
     }
 
-    // divulge how many times we are called
-    //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
+    // Careful check for staleness...
+    $context->reload_if_dirty();
 
-    if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid
-        //
-        // For the logged in user, we have $USER->access
-        // which will have all RAs and caps preloaded for
-        // course and above contexts.
-        //
-        // Contexts below courses && contexts that do not
-        // hang from courses are loaded into $USER->access
-        // on demand, and listed in $USER->access[loaded]
-        //
-        if ($context->contextlevel <= CONTEXT_COURSE) {
-            // Course and above are always preloaded
-            return has_capability_in_accessdata($capability, $context, $USER->access);
+    if ($USER->id == $userid) {
+        if (!isset($USER->access)) {
+            load_all_capabilities();
         }
-        // Load accessdata for below-the-course contexts
-        if (!path_inaccessdata($context->path,$USER->access)) {
-            // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
-            // $bt = debug_backtrace();
-            // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
-            load_subcontext($USER->id, $context, $USER->access);
-        }
-        return has_capability_in_accessdata($capability, $context, $USER->access);
-    }
+        $access =& $USER->access;
 
-    if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
-        load_user_accessdata($userid);
+    } else {
+        // make sure user accessdata is really loaded
+        get_user_accessdata($userid, true);
+        $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
     }
 
-    if ($context->contextlevel <= CONTEXT_COURSE) {
-        // Course and above are always preloaded
-        return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
-    }
-    // Load accessdata for below-the-course contexts as needed
-    if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
-        // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
-        // $bt = debug_backtrace();
-        // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
-        load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
+
+    // Load accessdata for below-the-course context if necessary,
+    // all contexts at and above all courses are already loaded
+    if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) {
+        load_course_context($userid, $coursecontext, $access);
     }
-    return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
+
+    return has_capability_in_accessdata($capability, $context, $access);
 }
 
 /**
@@ -724,21 +482,14 @@ function has_capability($capability, $context, $user = null, $doanything = true)
  * the capabilities that most users are likely to have first in the list for best
  * performance.
  *
- * There are probably tricks that could be done to improve the performance here, for example,
- * check the capabilities that are already cached first.
- *
  * @see has_capability()
  * @param array $capabilities an array of capability names.
- * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
+ * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
  * @param boolean $doanything If false, ignore effect of admin role assignment
  * @return boolean true if the user has any of these capabilities. Otherwise false.
  */
-function has_any_capability($capabilities, $context, $userid = null, $doanything = true) {
-    if (!is_array($capabilities)) {
-        debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array');
-        return false;
-    }
+function has_any_capability(array $capabilities, context $context, $userid = null, $doanything = true) {
     foreach ($capabilities as $capability) {
         if (has_capability($capability, $context, $userid, $doanything)) {
             return true;
@@ -754,21 +505,14 @@ function has_any_capability($capabilities, $context, $userid = null, $doanything
  * the capabilities that fewest users are likely to have first in the list for best
  * performance.
  *
- * There are probably tricks that could be done to improve the performance here, for example,
- * check the capabilities that are already cached first.
- *
  * @see has_capability()
  * @param array $capabilities an array of capability names.
- * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
+ * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
  * @param boolean $doanything If false, ignore effect of admin role assignment
  * @return boolean true if the user has all of these capabilities. Otherwise false.
  */
-function has_all_capabilities($capabilities, $context, $userid = null, $doanything = true) {
-    if (!is_array($capabilities)) {
-        debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
-        return false;
-    }
+function has_all_capabilities(array $capabilities, context $context, $userid = null, $doanything = true) {
     foreach ($capabilities as $capability) {
         if (!has_capability($capability, $context, $userid, $doanything)) {
             return false;
@@ -783,8 +527,8 @@ function has_all_capabilities($capabilities, $context, $userid = null, $doanythi
  * Please note that use of proper capabilities is always encouraged,
  * this function is supposed to be used from core or for temporary hacks.
  *
- * @param   int|object  $user_or_id user id or user object
- * @returns bool true if user is one of the administrators, false otherwise
+ * @param  int|stdClass  $user_or_id user id or user object
+ * @return bool true if user is one of the administrators, false otherwise
  */
 function is_siteadmin($user_or_id = null) {
     global $CFG, $USER;
@@ -797,7 +541,6 @@ function is_siteadmin($user_or_id = null) {
         return false;
     }
     if (!empty($user_or_id->id)) {
-        // we support
         $userid = $user_or_id->id;
     } else {
         $userid = $user_or_id;
@@ -811,8 +554,8 @@ function is_siteadmin($user_or_id = null) {
  * Returns true if user has at least one role assign
  * of 'coursecontact' role (is potentially listed in some course descriptions).
  *
- * @param $userid
- * @return stdClass
+ * @param int $userid
+ * @return bool
  */
 function has_coursecontact_role($userid) {
     global $DB, $CFG;
@@ -826,52 +569,11 @@ function has_coursecontact_role($userid) {
     return $DB->record_exists_sql($sql, array('userid'=>$userid));
 }
 
-/**
- * @param string $path
- * @return string
- */
-function get_course_from_path($path) {
-    // assume that nothing is more than 1 course deep
-    if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
-        return $matches[1];
-    }
-    return false;
-}
-
-/**
- * @param string $path
- * @param array $accessdata
- * @return bool
- */
-function path_inaccessdata($path, $accessdata) {
-    if (empty($accessdata['loaded'])) {
-        return false;
-    }
-
-    // assume that contexts hang from sys or from a course
-    // this will only work well with stuff that hangs from a course
-    if (in_array($path, $accessdata['loaded'], true)) {
-            // error_log("found it!");
-        return true;
-    }
-    $base = '/' . SYSCONTEXTID;
-    while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
-        $path = $matches[1];
-        if ($path === $base) {
-            return false;
-        }
-        if (in_array($path, $accessdata['loaded'], true)) {
-            return true;
-        }
-    }
-    return false;
-}
-
 /**
  * Does the user have a capability to do something?
  *
  * Walk the accessdata array and return true/false.
- * Deals with prohibits, roleswitching, aggregating
+ * Deals with prohibits, role switching, aggregating
  * capabilities, etc.
  *
  * The main feature of here is being FAST and with no
@@ -879,12 +581,6 @@ function path_inaccessdata($path, $accessdata) {
  *
  * Notes:
  *
- * Switch Roles exits early
- * ------------------------
- * cap checks within a switchrole need to exit early
- * in our bottom up processing so they don't "see" that
- * there are real RAs that can do all sorts of things.
- *
  * Switch Role merges with default role
  * ------------------------------------
  * If you are a teacher in course X, you have at least
@@ -901,26 +597,25 @@ function path_inaccessdata($path, $accessdata) {
  * and then verify if user has at least one role with allow
  * and at the same time no role with prohibit.
  *
+ * @private
  * @param string $capability
- * @param object $context
+ * @param context $context
  * @param array $accessdata
  * @return bool
  */
-function has_capability_in_accessdata($capability, $context, array $accessdata) {
+function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
     global $CFG;
 
-    if (empty($context->id)) {
-        throw new coding_exception('Invalid context specified');
-    }
-
     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
-    $contextids = explode('/', trim($context->path, '/'));
-    $paths = array($context->path);
-    while ($contextids) {
-        array_pop($contextids);
-        $paths[] = '/' . implode('/', $contextids);
+    $path = $context->path;
+    $paths = array($path);
+    while($path = rtrim($path, '0123456789')) {
+        $path = rtrim($path, '/');
+        if ($path === '') {
+            break;
+        }
+        $paths[] = $path;
     }
-    unset($contextids);
 
     $roles = array();
     $switchedrole = false;
@@ -950,56 +645,25 @@ function has_capability_in_accessdata($capability, $context, array $accessdata)
     }
 
     // Now find out what access is given to each role, going bottom-->up direction
+    $allowed = false;
     foreach ($roles as $roleid => $ignored) {
         foreach ($paths as $path) {
             if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
                 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
-                if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) {
+                if ($perm === CAP_PROHIBIT) {
+                    // any CAP_PROHIBIT found means no permission for the user
+                    return false;
+                }
+                if (is_null($roles[$roleid])) {
                     $roles[$roleid] = $perm;
                 }
             }
         }
-    }
-    // any CAP_PROHIBIT found means no permission for the user
-    if (array_search(CAP_PROHIBIT, $roles) !== false) {
-        return false;
+        // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
+        $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
     }
 
-    // at least one CAP_ALLOW means the user has a permission
-    return (array_search(CAP_ALLOW, $roles) !== false);
-}
-
-/**
- * @param object $context
- * @param array $accessdata
- * @return array
- */
-function aggregate_roles_from_accessdata($context, $accessdata) {
-
-    $path = $context->path;
-
-    // build $contexts as a list of "paths" of the current
-    // contexts and parents with the order top-to-bottom
-    $contexts = array($path);
-    while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
-        $path = $matches[1];
-        array_unshift($contexts, $path);
-    }
-
-    $cc = count($contexts);
-
-    $roles = array();
-    // From the bottom up...
-    for ($n=$cc-1; $n>=0; $n--) {
-        $ctxp = $contexts[$n];
-        if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
-            // Found assignments on this leaf
-            $addroles = $accessdata['ra'][$ctxp];
-            $roles    = array_merge($roles, $addroles);
-        }
-    }
-
-    return array_unique($roles);
+    return $allowed;
 }
 
 /**
@@ -1014,343 +678,269 @@ function aggregate_roles_from_accessdata($context, $accessdata) {
  * @see has_capability()
  *
  * @param string $capability the name of the capability to check. For example mod/forum:view
- * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
- * @param integer $userid A user id. By default (null) checks the permissions of the current user.
+ * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
+ * @param int $userid A user id. By default (null) checks the permissions of the current user.
  * @param bool $doanything If false, ignore effect of admin role assignment
- * @param string $errorstring The error string to to user. Defaults to 'nopermissions'.
+ * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
  * @return void terminates with an error if the user does not have the given capability.
  */
-function require_capability($capability, $context, $userid = null, $doanything = true,
+function require_capability($capability, context $context, $userid = null, $doanything = true,
                             $errormessage = 'nopermissions', $stringfile = '') {
     if (!has_capability($capability, $context, $userid, $doanything)) {
         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
     }
 }
 
-/**
- * Get an array of courses where cap requested is available
- * and user is enrolled, this can be relatively slow.
- *
- * @param string $capability - name of the capability
- * @param array  $accessdata_ignored
- * @param bool   $doanything_ignored
- * @param string $sort - sorting fields - prefix each fieldname with "c."
- * @param array  $fields - additional fields you are interested in...
- * @param int    $limit_ignored
- * @return array $courses - ordered array of course objects - see notes above
- */
-function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort = 'c.sortorder ASC', $fields = null, $limit_ignored = 0) {
-
-    //TODO: this should be most probably deprecated
-
-    $courses = enrol_get_users_courses($userid, true, $fields, $sort);
-    foreach ($courses as $id=>$course) {
-        $context = get_context_instance(CONTEXT_COURSE, $id);
-        if (!has_capability($cap, $context, $userid)) {
-            unset($courses[$id]);
-        }
-    }
-
-    return $courses;
-}
-
-
 /**
  * Return a nested array showing role assignments
  * all relevant role capabilities for the user at
  * site/course_category/course levels
  *
  * We do _not_ delve deeper than courses because the number of
- * overrides at the module/block levels is HUGE.
+ * overrides at the module/block levels can be HUGE.
  *
- * [ra]   => [/path/][]=roleid
- * [rdef] => [/path/:roleid][capability]=permission
- * [loaded] => array('/path', '/path')
+ * [ra]   => [/path][roleid]=roleid
+ * [rdef] => [/path:roleid][capability]=permission
  *
+ * @private
  * @param int $userid - the id of the user
- * @return array
+ * @return array access info array
  */
 function get_user_access_sitewide($userid) {
-    global $CFG, $DB;
+    global $CFG, $DB, $ACCESSLIB_PRIVATE;
 
-    /* Get in 3 cheap DB queries...
+    /* Get in a few cheap DB queries...
      * - role assignments
      * - relevant role caps
      *   - above and within this user's RAs
      *   - below this user's RAs - limited to course level
      */
 
-    $accessdata = array(); // named list
-    $accessdata['ra']     = array();
-    $accessdata['rdef']   = array();
-    $accessdata['loaded'] = array();
+    // raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef
+    $raparents = array();
+    $accessdata = get_empty_accessdata();
 
-    //
-    // Role assignments
-    //
+    // start with the default role
+    if (!empty($CFG->defaultuserroleid)) {
+        $syscontext = context_system::instance();
+        $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
+        $raparents[$CFG->defaultuserroleid][$syscontext->path] = $syscontext->path;
+    }
+
+    // load the "default frontpage role"
+    if (!empty($CFG->defaultfrontpageroleid)) {
+        $frontpagecontext = context_course::instance(get_site()->id);
+        if ($frontpagecontext->path) {
+            $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
+            $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->path] = $frontpagecontext->path;
+        }
+    }
+
+    // preload every assigned role at and above course context
     $sql = "SELECT ctx.path, ra.roleid
               FROM {role_assignments} ra
-              JOIN {context} ctx ON ctx.id=ra.contextid
-             WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE;
-    $params = array($userid);
-    $rs = $DB->get_recordset_sql($sql, $params);
+              JOIN {context} ctx ON ctx.id = ra.contextid
+         LEFT JOIN {context} cctx
+                   ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
+             WHERE ra.userid = :userid AND cctx.id IS NULL";
 
-    //
-    // raparents collects paths & roles we need to walk up
-    // the parenthood to build the rdef
-    //
-    $raparents = array();
-    if ($rs) {
-        foreach ($rs as $ra) {
-            // RAs leafs are arrays to support multi
-            // role assignments...
-            if (!isset($accessdata['ra'][$ra->path])) {
-                $accessdata['ra'][$ra->path] = array();
-            }
-            $accessdata['ra'][$ra->path][$ra->roleid] = $ra->roleid;
 
-            // Concatenate as string the whole path (all related context)
-            // for this role. This is damn faster than using array_merge()
-            // Will unique them later
-            if (isset($raparents[$ra->roleid])) {
-                $raparents[$ra->roleid] .= $ra->path;
-            } else {
-                $raparents[$ra->roleid] = $ra->path;
-            }
-        }
-        unset($ra);
-        $rs->close();
+    $params = array('userid'=>$userid);
+    $rs = $DB->get_recordset_sql($sql, $params);
+    foreach ($rs as $ra) {
+        // RAs leafs are arrays to support multi-role assignments...
+        $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
+        $raparents[$ra->roleid][$ra->path] = $ra->path;
     }
+    $rs->close();
 
-    // Walk up the tree to grab all the roledefs
-    // of interest to our user...
-    //
-    // NOTE: we use a series of IN clauses here - which
-    // might explode on huge sites with very convoluted nesting of
-    // categories... - extremely unlikely that the number of categories
-    // and roletypes is so large that we hit the limits of IN()
-    $clauses = '';
-    $cparams = array();
-    foreach ($raparents as $roleid=>$strcontexts) {
-        $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/'))));
-        if ($contexts ==! '') {
-            if ($clauses) {
-                $clauses .= ' OR ';
-            }
-            $clauses .= "(roleid=? AND contextid IN ($contexts))";
-            $cparams[] = $roleid;
-        }
+    if (empty($raparents)) {
+        return $accessdata;
     }
 
-    if ($clauses !== '') {
-        $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
-                  FROM {role_capabilities} rc
-                  JOIN {context} ctx ON rc.contextid=ctx.id
-                 WHERE $clauses";
+    // now get overrides of interesting roles in all interesting child contexts
+    // hopefully we will not run out of SQL limits here,
+    // users would have to have very many roles above course context...
+    $sqls = array();
+    $params = array();
 
-        unset($clauses);
-        $rs = $DB->get_recordset_sql($sql, $cparams);
+    static $cp = 0;
+    foreach ($raparents as $roleid=>$paths) {
+        $cp++;
+        list($paths, $rparams) = $DB->get_in_or_equal($paths, SQL_PARAMS_NAMED, 'p'.$cp.'_');
+        $params = array_merge($params, $rparams);
+        $params['r'.$cp] = $roleid;
+        $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission
+                     FROM {role_capabilities} rc
+                     JOIN {context} ctx
+                          ON (ctx.id = rc.contextid)
+                LEFT JOIN {context} cctx
+                          ON (cctx.contextlevel = ".CONTEXT_COURSE."
+                              AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
+                     JOIN {context} pctx
+                          ON (pctx.path $paths
+                              AND (ctx.id = pctx.id
+                                   OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")."
+                                   OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'")."))
+                    WHERE rc.roleid = :r{$cp}
+                          AND cctx.id IS NULL)";
+    }
+
+    // fixed capability order is necessary for rdef dedupe
+    $rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params);
 
-        if ($rs) {
-            foreach ($rs as $rd) {
-                $k = "{$rd->path}:{$rd->roleid}";
-                $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
-            }
-            unset($rd);
-            $rs->close();
-        }
+    foreach ($rs as $rd) {
+        $k = $rd->path.':'.$rd->roleid;
+        $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
     }
+    $rs->close();
 
-    //
-    // Overrides for the role assignments IN SUBCONTEXTS
-    // (though we still do _not_ go below the course level.
-    //
-    // NOTE that the JOIN w sctx is with 3-way triangulation to
-    // catch overrides to the applicable role in any subcontext, based
-    // on the path field of the parent.
-    //
-    $sql = "SELECT sctx.path, ra.roleid,
-                   ctx.path AS parentpath,
-                   rco.capability, rco.permission
-              FROM {role_assignments} ra
-              JOIN {context} ctx
-                   ON ra.contextid=ctx.id
-              JOIN {context} sctx
-                   ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " )
-              JOIN {role_capabilities} rco
-                   ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
-             WHERE ra.userid = ?
-                   AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
-                   AND sctx.contextlevel <= ".CONTEXT_COURSE."
-          ORDER BY sctx.depth, sctx.path, ra.roleid";
-    $params = array($userid);
-    $rs = $DB->get_recordset_sql($sql, $params);
-    if ($rs) {
-        foreach ($rs as $rd) {
-            $k = "{$rd->path}:{$rd->roleid}";
-            $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
+    // share the role definitions
+    foreach ($accessdata['rdef'] as $k=>$unused) {
+        if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
+            $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
         }
-        unset($rd);
-        $rs->close();
+        $accessdata['rdef_count']++;
+        $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
     }
+
     return $accessdata;
 }
 
 /**
- * Add to the access ctrl array the data needed by a user for a given context
+ * Add to the access ctrl array the data needed by a user for a given course.
  *
- * @param integer $userid the id of the user
- * @param object $context needs path!
- * @param array $accessdata accessdata array
- * @return void
+ * This function injects all course related access info into the accessdata array.
+ *
+ * @private
+ * @param int $userid the id of the user
+ * @param context_course $coursecontext course context
+ * @param array $accessdata accessdata array (modified)
+ * @return void modifies $accessdata parameter
  */
-function load_subcontext($userid, $context, &$accessdata) {
-    global $CFG, $DB;
+function load_course_context($userid, context_course $coursecontext, &$accessdata) {
+    global $DB, $CFG, $ACCESSLIB_PRIVATE;
 
-    /* Get the additional RAs and relevant rolecaps
-     * - role assignments - with role_caps
-     * - relevant role caps
-     *   - above this user's RAs
-     *   - below this user's RAs - limited to course level
-     */
+    if (empty($coursecontext->path)) {
+        // weird, this should not happen
+        return;
+    }
 
-    $base = "/" . SYSCONTEXTID;
+    if (isset($accessdata['loaded'][$coursecontext->instanceid])) {
+        // already loaded, great!
+        return;
+    }
 
-    //
-    // Replace $context with the target context we will
-    // load. Normally, this will be a course context, but
-    // may be a different top-level context.
-    //
-    // We have 3 cases
-    //
-    // - Course
-    // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
-    // - BLOCK/MODULE/GROUP hanging from a course
-    //
-    // For course contexts, we _already_ have the RAs
-    // but the cost of re-fetching is minimal so we don't care.
-    //
-    if ($context->contextlevel !== CONTEXT_COURSE
-        && $context->path !== "$base/{$context->id}") {
-        // Case BLOCK/MODULE/GROUP hanging from a course
-        // Assumption: the course _must_ be our parent
-        // If we ever see stuff nested further this needs to
-        // change to do 1 query over the exploded path to
-        // find out which one is the course
-        $courses = explode('/',get_course_from_path($context->path));
-        $targetid = array_pop($courses);
-        $context = get_context_instance_by_id($targetid);
+    $roles = array();
 
-    }
+    if (empty($userid)) {
+        if (!empty($CFG->notloggedinroleid)) {
+            $roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid;
+        }
 
-    //
-    // Role assignments in the context and below
-    //
-    $sql = "SELECT ctx.path, ra.roleid
-              FROM {role_assignments} ra
-              JOIN {context} ctx
-                   ON ra.contextid=ctx.id
-             WHERE ra.userid = ?
-                   AND (ctx.path = ? OR ctx.path LIKE ?)
-          ORDER BY ctx.depth, ctx.path, ra.roleid";
-    $params = array($userid, $context->path, $context->path."/%");
-    $rs = $DB->get_recordset_sql($sql, $params);
+    } else if (isguestuser($userid)) {
+        if ($guestrole = get_guest_role()) {
+            $roles[$guestrole->id] = $guestrole->id;
+        }
 
-    //
-    // Read in the RAs, preventing duplicates
-    //
-    if ($rs) {
-        $localroles = array();
-        $lastseen  = '';
+    } else {
+        // Interesting role assignments at, above and below the course context
+        list($parentsaself, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
+        $params['userid'] = $userid;
+        $params['children'] = $coursecontext->path."/%";
+        $sql = "SELECT ra.*, ctx.path
+                  FROM {role_assignments} ra
+                  JOIN {context} ctx ON ra.contextid = ctx.id
+                 WHERE ra.userid = :userid AND (ctx.id $parentsaself OR ctx.path LIKE :children)";
+        $rs = $DB->get_recordset_sql($sql, $params);
+
+        // add missing role definitions
         foreach ($rs as $ra) {
-            if (!isset($accessdata['ra'][$ra->path])) {
-                $accessdata['ra'][$ra->path] = array();
-            }
-            // only add if is not a repeat caused
-            // by capability join...
-            // (this check is cheaper than in_array())
-            if ($lastseen !== $ra->path.':'.$ra->roleid) {
-                $lastseen = $ra->path.':'.$ra->roleid;
-                $accessdata['ra'][$ra->path][$ra->roleid] = $ra->roleid;
-                array_push($localroles,           $ra->roleid);
-            }
+            $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
+            $roles[$ra->roleid] = $ra->roleid;
         }
         $rs->close();
-    }
 
-    //
-    // Walk up and down the tree to grab all the roledefs
-    // of interest to our user...
-    //
-    // NOTES
-    // - we use IN() but the number of roles is very limited.
-    //
-    $courseroles    = aggregate_roles_from_accessdata($context, $accessdata);
+        // add the "default frontpage role" when on the frontpage
+        if (!empty($CFG->defaultfrontpageroleid)) {
+            $frontpagecontext = context_course::instance(get_site()->id);
+            if ($frontpagecontext->id == $coursecontext->id) {
+                $roles[$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
+            }
+        }
 
-    // Do we have any interesting "local" roles?
-    $localroles = array_diff($localroles,$courseroles); // only "new" local roles
-    $wherelocalroles='';
-    if (count($localroles)) {
-        // Role defs for local roles in 'higher' contexts...
-        $contexts = substr($context->path, 1); // kill leading slash
-        $contexts = str_replace('/', ',', $contexts);
-        $localroleids = implode(',',$localroles);
-        $wherelocalroles="OR (rc.roleid IN ({$localroleids})
-                              AND ctx.id IN ($contexts))" ;
+        // do not forget the default role
+        if (!empty($CFG->defaultuserroleid)) {
+            $roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
+        }
     }
 
-    // We will want overrides for all of them
-    $whereroles = '';
-    if ($roleids  = implode(',',array_merge($courseroles,$localroles))) {
-        $whereroles = "rc.roleid IN ($roleids) AND";
+    if (!$roles) {
+        // weird, default roles must be missing...
+        $accessdata['loaded'][$coursecontext->instanceid] = 1;
+        return;
     }
+
+    // now get overrides of interesting roles in all interesting contexts (this course + children + parents)
+    $params = array('c'=>$coursecontext->id);
+    list($parentsaself, $rparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
+    $params = array_merge($params, $rparams);
+    list($roleids, $rparams) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED, 'r_');
+    $params = array_merge($params, $rparams);
+
     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
-              FROM {role_capabilities} rc
-              JOIN {context} ctx
-                   ON rc.contextid=ctx.id
-             WHERE ($whereroles
-                    (ctx.id=? OR ctx.path LIKE ?))
-                   $wherelocalroles
-          ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
-    $params = array($context->id, $context->path."/%");
+                 FROM {role_capabilities} rc
+                 JOIN {context} ctx
+                      ON (ctx.id = rc.contextid)
+                 JOIN {context} cctx
+                      ON (cctx.id = :c
+                          AND (ctx.id $parentsaself OR ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'")."))
+                WHERE rc.roleid $roleids
+             ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
+    $rs = $DB->get_recordset_sql($sql, $params);
 
     $newrdefs = array();
-    $rs = $DB->get_recordset_sql($sql, $params);
     foreach ($rs as $rd) {
-        $k = "{$rd->path}:{$rd->roleid}";
-        if (!array_key_exists($k, $newrdefs)) {
-            $newrdefs[$k] = array();
+        $k = $rd->path.':'.$rd->roleid;
+        if (isset($accessdata['rdef'][$k])) {
+            continue;
         }
-        $newrdefs[$k][$rd->capability] = $rd->permission;
+        $newrdefs[$k][$rd->capability] = (int)$rd->permission;
     }
     $rs->close();
 
-    compact_rdefs($newrdefs);
-    foreach ($newrdefs as $key=>$value) {
-        $accessdata['rdef'][$key] =& $newrdefs[$key];
+    // share new role definitions
+    foreach ($newrdefs as $k=>$unused) {
+        if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
+            $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
+        }
+        $accessdata['rdef_count']++;
+        $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
     }
 
-    // error_log("loaded {$context->path}");
-    $accessdata['loaded'][] = $context->path;
+    $accessdata['loaded'][$coursecontext->instanceid] = 1;
+
+    // we want to deduplicate the USER->access from time to time, this looks like a good place,
+    // because we have to do it before the end of session
+    dedupe_user_access();
 }
 
 /**
  * Add to the access ctrl array the data needed by a role for a given context.
  *
  * The data is added in the rdef key.
- *
  * This role-centric function is useful for role_switching
- * and to get an overview of what a role gets under a
- * given context and below...
+ * and temporary course roles.
  *
- * @param integer $roleid the id of the user
- * @param object $context needs path!
- * @param array $accessdata accessdata array null by default
+ * @private
+ * @param int $roleid the id of the user
+ * @param context $context needs path!
+ * @param array $accessdata accessdata array (is modified)
  * @return array
  */
-function get_role_access_bycontext($roleid, $context, $accessdata = null) {
-    global $CFG, $DB;
+function load_role_access_by_context($roleid, context $context, &$accessdata) {
+    global $DB, $ACCESSLIB_PRIVATE;
 
     /* Get the relevant rolecaps into rdef
      * - relevant role caps
@@ -1358,306 +948,277 @@ function get_role_access_bycontext($roleid, $context, $accessdata = null) {
      *   - below this ctx
      */
 
-    if (is_null($accessdata)) {
-        $accessdata           = array(); // named list
-        $accessdata['ra']     = array();
-        $accessdata['rdef']   = array();
-        $accessdata['loaded'] = array();
+    if (empty($context->path)) {
+        // weird, this should not happen
+        return;
     }
 
-    $contexts = substr($context->path, 1); // kill leading slash
-    $contexts = str_replace('/', ',', $contexts);
+    list($parentsaself, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
+    $params['roleid'] = $roleid;
+    $params['childpath'] = $context->path.'/%';
 
-    //
-    // Walk up and down the tree to grab all the roledefs
-    // of interest to our role...
-    //
-    // NOTE: we use an IN clauses here - which
-    // might explode on huge sites with very convoluted nesting of
-    // categories... - extremely unlikely that the number of nested
-    // categories is so large that we hit the limits of IN()
-    //
     $sql = "SELECT ctx.path, rc.capability, rc.permission
               FROM {role_capabilities} rc
-              JOIN {context} ctx
-                   ON rc.contextid=ctx.id
-             WHERE rc.roleid=? AND
-                   ( ctx.id IN ($contexts) OR
-                    ctx.path LIKE ? )
-          ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
-    $params = array($roleid, $context->path."/%");
-
+              JOIN {context} ctx ON (rc.contextid = ctx.id)
+             WHERE rc.roleid = :roleid AND (ctx.id $parentsaself OR ctx.path LIKE :childpath)
+          ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
     $rs = $DB->get_recordset_sql($sql, $params);
+
+    $newrdefs = array();
     foreach ($rs as $rd) {
-        $k = "{$rd->path}:{$roleid}";
-        $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
+        $k = $rd->path.':'.$roleid;
+        if (isset($accessdata['rdef'][$k])) {
+            continue;
+        }
+        $newrdefs[$k][$rd->capability] = (int)$rd->permission;
     }
     $rs->close();
 
+    // share new role definitions
+    foreach ($newrdefs as $k=>$unused) {
+        if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
+            $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
+        }
+        $accessdata['rdef_count']++;
+        $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
+    }
+}
+
+/**
+ * Returns empty accessdata structure.
+ * @return array empt accessdata
+ */
+function get_empty_accessdata() {
+    $accessdata               = array(); // named list
+    $accessdata['ra']         = array();
+    $accessdata['rdef']       = array();
+    $accessdata['rdef_count'] = 0;       // this bloody hack is necessary because count($array) is slooooowwww in PHP
+    $accessdata['rdef_lcc']   = 0;       // rdef_count during the last compression
+    $accessdata['loaded']     = array(); // loaded course contexts
+    $accessdata['time']       = time();
+
     return $accessdata;
 }
 
 /**
- * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global
- *
- * Used by has_capability() - but feel free
- * to call it if you are about to run a BIG
- * cron run across a bazillion users.
+ * Get accessdata for a given user.
  *
+ * @private
  * @param int $userid
- * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid]
+ * @param bool $preloadonly true means do not return access array
+ * @return array accessdata
  */
-function load_user_accessdata($userid) {
-    global $CFG, $ACCESSLIB_PRIVATE;
-
-    $base = '/'.SYSCONTEXTID;
+function get_user_accessdata($userid, $preloadonly=false) {
+    global $CFG, $ACCESSLIB_PRIVATE, $USER;
 
-    $accessdata = get_user_access_sitewide($userid);
-    $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
-    //
-    // provide "default role" & set 'dr'
-    //
-    if (!empty($CFG->defaultuserroleid)) {
-        $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
-        if (!isset($accessdata['ra'][$base])) {
-            $accessdata['ra'][$base] = array();
+    if (!empty($USER->acces['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) {
+        // share rdef from USER session with rolepermissions cache in order to conserve memory
+        foreach($USER->acces['rdef'] as $k=>$v) {
+            $ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->acces['rdef'][$k];
         }
-        $accessdata['ra'][$base][$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
-        $accessdata['dr'] = $CFG->defaultuserroleid;
+        $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->acces;
     }
 
-    //
-    // provide "default frontpage role"
-    //
-    if (!empty($CFG->defaultfrontpageroleid)) {
-        $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
-        $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
-        if (!isset($accessdata['ra'][$base])) {
-            $accessdata['ra'][$base] = array();
+    if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
+        if (empty($userid)) {
+            if (!empty($CFG->notloggedinroleid)) {
+                $accessdata = get_role_access($CFG->notloggedinroleid);
+            } else {
+                // weird
+                return get_empty_accessdata();
+            }
+
+        } else if (isguestuser($userid)) {
+            if ($guestrole = get_guest_role()) {
+                $accessdata = get_role_access($guestrole->id);
+            } else {
+                //weird
+                return get_empty_accessdata();
+            }
+
+        } else {
+            $accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role
         }
-        $accessdata['ra'][$base][$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
-    }
-    // for dirty timestamps in cron
-    $accessdata['time'] = time();
 
-    $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
-    compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']);
+        $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
+    }
 
-    return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
+    if ($preloadonly) {
+        return;
+    } else {
+        return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
+    }
 }
 
 /**
- * Use shared copy of role definitions stored in ACCESSLIB_PRIVATE->roledefinitions;
+ * Try to minimise the size of $USER->access by eliminating duplicate override storage,
+ * this function looks for contexts with the same overrides and shares them.
  *
- * @param array $rdefs array of role definitions in contexts
+ * @private
+ * @return void
  */
-function compact_rdefs(&$rdefs) {
-    global $ACCESSLIB_PRIVATE;
+function dedupe_user_access() {
+    global $USER;
 
-    /*
-     * This is a basic sharing only, we could also
-     * use md5 sums of values. The main purpose is to
-     * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array.
-     */
+    if (CLI_SCRIPT) {
+        // no session in CLI --> no compression necessary
+        return;
+    }
 
-    foreach ($rdefs as $key => $value) {
-        if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) {
-            $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key];
+    if (empty($USER->access['rdef_count'])) {
+        // weird, this should not happen
+        return;
+    }
+
+    // the rdef is growing only, we never remove stuff from it, the rdef_lcc helps us to detect new stuff in rdef
+    if ($USER->access['rdef_count'] - $USER->access['rdef_lcc'] > 10) {
+        // do not compress after each change, wait till there is more stuff to be done
+        return;
+    }
+
+    $hashmap = array();
+    foreach ($USER->access['rdef'] as $k=>$def) {
+        $hash = sha1(serialize($def));
+        if (isset($hashmap[$hash])) {
+            $USER->access['rdef'][$k] =& $hashmap[$hash];
+        } else {
+            $hashmap[$hash] =& $USER->access['rdef'][$k];
         }
-        $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key];
     }
+
+    $USER->access['rdef_lcc'] = $USER->access['rdef_count'];
 }
 
 /**
  * A convenience function to completely load all the capabilities
- * for the current user.   This is what gets called from complete_user_login()
- * for example. Call it only _after_ you've setup $USER and called
- * check_enrolment_plugins();
+ * for the current user. It is called from has_capability() and functions change permissions.
+ *
+ * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
  * @see check_enrolment_plugins()
  *
+ * @private
  * @return void
  */
 function load_all_capabilities() {
-    global $CFG, $ACCESSLIB_PRIVATE;
-
-    //NOTE: we can not use $USER here because it may no be linked to $_SESSION['USER'] yet!
+    global $USER;
 
     // roles not installed yet - we are in the middle of installation
     if (during_initial_install()) {
         return;
     }
 
-    $base = '/'.SYSCONTEXTID;
-
-    if (isguestuser($_SESSION['USER'])) {
-        $guest = get_guest_role();
-
-        // Load the rdefs
-        $_SESSION['USER']->access = get_role_access($guest->id);
-        // Put the ghost enrolment in place...
-        $_SESSION['USER']->access['ra'][$base] = array($guest->id => $guest->id);
-
-
-    } else if (!empty($_SESSION['USER']->id)) { // can not use isloggedin() yet
-
-        $accessdata = get_user_access_sitewide($_SESSION['USER']->id);
-
-        //
-        // provide "default role" & set 'dr'
-        //
-        if (!empty($CFG->defaultuserroleid)) {
-            $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
-            if (!isset($accessdata['ra'][$base])) {
-                $accessdata['ra'][$base] = array();
-            }
-            $accessdata['ra'][$base][$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
-            $accessdata['dr'] = $CFG->defaultuserroleid;
-        }
-
-        $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
-
-        //
-        // provide "default frontpage role"
-        //
-        if (!empty($CFG->defaultfrontpageroleid)) {
-            $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
-            $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
-            if (!isset($accessdata['ra'][$base])) {
-                $accessdata['ra'][$base] = array();
-            }
-            $accessdata['ra'][$base][$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
-        }
-        $_SESSION['USER']->access = $accessdata;
-
-    } else if (!empty($CFG->notloggedinroleid)) {
-        $_SESSION['USER']->access = get_role_access($CFG->notloggedinroleid);
-        $_SESSION['USER']->access['ra'][$base] = array($CFG->notloggedinroleid => $CFG->notloggedinroleid);
+    if (!isset($USER->id)) {
+        // this should not happen
+        $USER->id = 0;
     }
 
-    // Timestamp to read dirty context timestamps later
-    $_SESSION['USER']->access['time'] = time();
-    $ACCESSLIB_PRIVATE->dirtycontexts = array();
+    unset($USER->access);
+    $USER->access = get_user_accessdata($USER->id);
+
+    // deduplicate the overrides to minimize session size
+    dedupe_user_access();
 
     // Clear to force a refresh
-    unset($_SESSION['USER']->mycourses);
+    unset($USER->mycourses);
+    unset($USER->enrol);
 }
 
 /**
  * A convenience function to completely reload all the capabilities
  * for the current user when roles have been updated in a relevant
  * context -- but PRESERVING switchroles and loginas.
+ * This function resets all accesslib and context caches.
  *
  * That is - completely transparent to the user.
  *
- * Note: rewrites $USER->access completely.
+ * Note: reloads $USER->access completely.
  *
+ * @private
  * @return void
  */
 function reload_all_capabilities() {
-    global $USER, $DB;
+    global $USER, $DB, $ACCESSLIB_PRIVATE;
 
-    // error_log("reloading");
     // copy switchroles
     $sw = array();
     if (isset($USER->access['rsw'])) {
         $sw = $USER->access['rsw'];
-        // error_log(print_r($sw,1));
     }
 
+    accesslib_clear_all_caches(true);
     unset($USER->access);
-    unset($USER->mycourses);
+    $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page
 
     load_all_capabilities();
 
     foreach ($sw as $path => $roleid) {
-        $context = $DB->get_record('context', array('path'=>$path));
-        role_switch($roleid, $context);
+        if ($record = $DB->get_record('context', array('path'=>$path))) {
+            $context = context::instance_by_id($record->id);
+            role_switch($roleid, $context);
+        }
     }
-
 }
 
 /**
- * Adds a temp role to an accessdata array.
- *
- * Useful for the "temporary guest" access
- * we grant to logged-in users.
+ * Adds a temp role to current USER->access array.
  *
- * Note - assumes a course context!
+ * Useful for the "temporary guest" access we grant to logged-in users.
  *
- * @param object $content
+ * @param context_course $coursecontext
  * @param int $roleid
- * @param array $accessdata
- * @return array Returns access data
+ * @return void
  */
-function load_temp_role($context, $roleid, array $accessdata) {
-    global $CFG, $DB;
+function load_temp_course_role(context_course $coursecontext, $roleid) {
+    global $USER;
 
-    //
-    // Load rdefs for the role in -
-    // - this context
-    // - all the parents
-    // - and below - IOWs overrides...
-    //
+    //TODO: this gets removed if there are any dirty contexts, we should probably store list of these temp roles somewhere (skodak)
 
-    // turn the path into a list of context ids
-    $contexts = substr($context->path, 1); // kill leading slash
-    $contexts = str_replace('/', ',', $contexts);
+    if (empty($roleid)) {
+        debugging('invalid role specified in load_temp_course_role()');
+        return;
+    }
 
-    $sql = "SELECT ctx.path, rc.capability, rc.permission
-              FROM {context} ctx
-              JOIN {role_capabilities} rc
-                   ON rc.contextid=ctx.id
-             WHERE (ctx.id IN ($contexts)
-                    OR ctx.path LIKE ?)
-                   AND rc.roleid = ?
-          ORDER BY ctx.depth, ctx.path";
-    $params = array($context->path."/%", $roleid);
-    $rs = $DB->get_recordset_sql($sql, $params);
-    foreach ($rs as $rd) {
-        $k = "{$rd->path}:{$roleid}";
-        $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
+    if (!isset($USER->access)) {
+        load_all_capabilities();
     }
-    $rs->close();
 
-    //
-    // Say we loaded everything for the course context
-    // - which we just did - if the user gets a proper
-    // RA in this session, this data will need to be reloaded,
-    // but that is handled by the complete accessdata reload
-    //
-    array_push($accessdata['loaded'], $context->path);
+    $coursecontext->reload_if_dirty();
 
-    //
-    // Add the ghost RA
-    //
-    if (!isset($accessdata['ra'][$context->path])) {
-        $accessdata['ra'][$context->path] = array();
+    if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
+        return;
     }
-    $accessdata['ra'][$context->path][$roleid] = $roleid;
 
-    return $accessdata;
+    // load course stuff first
+    load_course_context($USER->id, $coursecontext, $USER->access);
+
+    $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
+
+    load_role_access_by_context($roleid, $coursecontext, $USER->access);
 }
 
 /**
- * Removes any extra guest roles from accessdata
- * @param object $context
- * @param array $accessdata
- * @return array access data
+ * Removes any extra guest roles from current USER->access array.
+ *
+ * @param context_course $coursecontext
+ * @return void
  */
-function remove_temp_roles($context, array $accessdata) {
+function remove_temp_course_roles(context_course $coursecontext) {
     global $DB, $USER;
+
+    if (empty($USER->access['ra'][$coursecontext->path])) {
+        //no roles here, weird
+        return;
+    }
+
     $sql = "SELECT DISTINCT ra.roleid AS id
               FROM {role_assignments} ra
              WHERE ra.contextid = :contextid AND ra.userid = :userid";
-    $ras = $DB->get_records_sql($sql, array('contextid'=>$context->id, 'userid'=>$USER->id));
+    $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
 
-    if ($ras) {
-        $accessdata['ra'][$context->path] = array_combine(array_keys($ras), array_keys($ras));
-    } else {
-        $accessdata['ra'][$context->path] = array();
+    $USER->access['ra'][$coursecontext->path] = array();
+    foreach($ras as $r) {
+        $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
     }
-
-    return $accessdata;
 }
 
 /**
@@ -1698,7 +1259,7 @@ function assign_legacy_capabilities($capability, $legacyperms) {
 
     foreach ($legacyperms as $type => $perm) {
 
-        $systemcontext = get_context_instance(CONTEXT_SYSTEM);
+        $systemcontext = context_system::instance();
         if ($type === 'admin') {
             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
             $type = 'manager';
@@ -1721,6 +1282,8 @@ function assign_legacy_capabilities($capability, $legacyperms) {
 }
 
 /**
+ * Verify capability risks.
+ *
  * @param object $capability a capability - a row from the capabilities table.
  * @return boolean whether this capability is safe - that is, whether people with the
  *      safeoverrides capability should be allowed to change it.
@@ -1729,4255 +1292,5627 @@ function is_safe_capability($capability) {
     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
 }
 
-/**********************************
- * Context Manipulation functions *
- **********************************/
-
 /**
- * Context creation - internal implementation.
- *
- * Create a new context record for use by all roles-related stuff
- * assumes that the caller has done the homework.
- *
- * DO NOT CALL THIS DIRECTLY, instead use {@link get_context_instance}!
+ * Get the local override (if any) for a given capability in a role in a context
  *
- * @param int $contextlevel
- * @param int $instanceid
- * @param int $strictness
- * @return object newly created context
+ * @param int $roleid
+ * @param int $contextid
+ * @param string $capability
+ * @return stdClass local capability override
  */
-function create_context($contextlevel, $instanceid, $strictness = IGNORE_MISSING) {
-    global $CFG, $DB;
-
-    if ($contextlevel == CONTEXT_SYSTEM) {
-        return get_system_context();
-    }
-
-    $context = new stdClass();
-    $context->contextlevel = $contextlevel;
-    $context->instanceid = $instanceid;
-
-    // Define $context->path based on the parent
-    // context. In other words... Who is your daddy?
-    $basepath  = '/' . SYSCONTEXTID;
-    $basedepth = 1;
-
-    $result = true;
-    $error_message = null;
-
-    switch ($contextlevel) {
-        case CONTEXT_COURSECAT:
-            $sql = "SELECT ctx.path, ctx.depth
-                      FROM {context}           ctx
-                      JOIN {course_categories} cc
-                           ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
-                     WHERE cc.id=?";
-            $params = array($instanceid);
-            if ($p = $DB->get_record_sql($sql, $params)) {
-                $basepath  = $p->path;
-                $basedepth = $p->depth;
-            } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), '*', $strictness)) {
-                if (empty($category->parent)) {
-                    // ok - this is a top category
-                } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
-                    $basepath  = $parent->path;
-                    $basedepth = $parent->depth;
-                } else {
-                    // wrong parent category - no big deal, this can be fixed later
-                    $basepath  = null;
-                    $basedepth = 0;
-                }
-            } else {
-                // incorrect category id
-                $error_message = "incorrect course category id ($instanceid)";
-                $result = false;
-            }
-            break;
+function get_local_override($roleid, $contextid, $capability) {
+    global $DB;
+    return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
+}
 
-        case CONTEXT_COURSE:
-            $sql = "SELECT ctx.path, ctx.depth
-                      FROM {context} ctx
-                      JOIN {course}  c
-                           ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
-                     WHERE c.id=? AND c.id !=" . SITEID;
-            $params = array($instanceid);
-            if ($p = $DB->get_record_sql($sql, $params)) {
-                $basepath  = $p->path;
-                $basedepth = $p->depth;
-            } else if ($course = $DB->get_record('course', array('id'=>$instanceid), '*', $strictness)) {
-                if ($course->id == SITEID) {
-                    //ok - no parent category
-                } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
-                    $basepath  = $parent->path;
-                    $basedepth = $parent->depth;
-                } else {
-                    // wrong parent category of course - no big deal, this can be fixed later
-                    $basepath  = null;
-                    $basedepth = 0;
-                }
-            } else if ($instanceid == SITEID) {
-                // no errors for missing site course during installation
-                return false;
-            } else {
-                // incorrect course id
-                $error_message = "incorrect course id ($instanceid)";
-                $result = false;
-            }
-            break;
+/**
+ * Returns context instance plus related course and cm instances
+ *
+ * @param int $contextid
+ * @return array of ($context, $course, $cm)
+ */
+function get_context_info_array($contextid) {
+    global $DB;
 
-        case CONTEXT_MODULE:
-            $sql = "SELECT ctx.path, ctx.depth
-                      FROM {context}        ctx
-                      JOIN {course_modules} cm
-                           ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
-                     WHERE cm.id=?";
-            $params = array($instanceid);
-            if ($p = $DB->get_record_sql($sql, $params)) {
-                $basepath  = $p->path;
-                $basedepth = $p->depth;
-            } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), '*', $strictness)) {
-                if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course, $strictness)) {
-                    $basepath  = $parent->path;
-                    $basedepth = $parent->depth;
-                } else {
-                    // course does not exist - modules can not exist without a course
-                    $error_message = "course does not exist ($cm->course) - modules can not exist without a course";
-                    $result = false;
-                }
-            } else {
-                // cm does not exist
-                $error_message = "cm with id $instanceid does not exist";
-                $result = false;
-            }
-            break;
+    $context = context::instance_by_id($contextid, MUST_EXIST);
+    $course  = null;
+    $cm      = null;
 
-        case CONTEXT_BLOCK:
-            $sql = "SELECT ctx.path, ctx.depth
-                      FROM {context} ctx
-                      JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id)
-                     WHERE bi.id = ?";
-            $params = array($instanceid, CONTEXT_COURSE);
-            if ($p = $DB->get_record_sql($sql, $params, '*', $strictness)) {
-                $basepath  = $p->path;
-                $basedepth = $p->depth;
-            } else {
-                // block does not exist
-                $error_message = 'block or parent context does not exist';
-                $result = false;
-            }
-            break;
-        case CONTEXT_USER:
-            // default to basepath
-            break;
-    }
+    if ($context->contextlevel == CONTEXT_COURSE) {
+        $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
 
-    // if grandparents unknown, maybe rebuild_context_path() will solve it later
-    if ($basedepth != 0) {
-        $context->depth = $basedepth+1;
-    }
+    } else if ($context->contextlevel == CONTEXT_MODULE) {
+        $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
+        $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 
-    if (!$result) {
-        debugging('Error: could not insert new context level "'.
-                  s($contextlevel).'", instance "'.
-                  s($instanceid).'". ' . $error_message);
+    } else if ($context->contextlevel == CONTEXT_BLOCK) {
+        $parent = $context->get_parent_context();
 
-        return false;
+        if ($parent->contextlevel == CONTEXT_COURSE) {
+            $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
+        } else if ($parent->contextlevel == CONTEXT_MODULE) {
+            $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
+            $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+        }
     }
 
-    $id = $DB->insert_record('context', $context);
-    // can't set the full path till we know the id!
-    if ($basedepth != 0 and !empty($basepath)) {
-        $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
-    }
-    return get_context_instance_by_id($id);
+    return array($context, $course, $cm);
 }
 
 /**
- * Returns system context or null if can not be created yet.
+ * Function that creates a role
  *
- * @param bool $cache use caching
- * @return mixed system context or null
+ * @param string $name role name
+ * @param string $shortname role short name
+ * @param string $description role description
+ * @param string $archetype
+ * @return int id or dml_exception
  */
-function get_system_context($cache = true) {
-    global $DB, $ACCESSLIB_PRIVATE;
-    if ($cache and defined('SYSCONTEXTID')) {
-        if (is_null($ACCESSLIB_PRIVATE->systemcontext)) {
-            $ACCESSLIB_PRIVATE->systemcontext = new stdClass();
-            $ACCESSLIB_PRIVATE->systemcontext->id           = SYSCONTEXTID;
-            $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM;
-            $ACCESSLIB_PRIVATE->systemcontext->instanceid   = 0;
-            $ACCESSLIB_PRIVATE->systemcontext->path         = '/'.SYSCONTEXTID;
-            $ACCESSLIB_PRIVATE->systemcontext->depth        = 1;
-        }
-        return $ACCESSLIB_PRIVATE->systemcontext;
-    }
-    try {
-        $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM));
-    } catch (dml_exception $e) {
-        //table does not exist yet, sorry
-        return null;
-    }
-
-    if (!$context) {
-        $context = new stdClass();
-        $context->contextlevel = CONTEXT_SYSTEM;
-        $context->instanceid   = 0;
-        $context->depth        = 1;
-        $context->path         = null; //not known before insert
+function create_role($name, $shortname, $description, $archetype = '') {
+    global $DB;
 
-        try {
-            $context->id = $DB->insert_record('context', $context);
-        } catch (dml_exception $e) {
-            // can not create context yet, sorry
-            return null;
-        }
+    if (strpos($archetype, 'moodle/legacy:') !== false) {
+        throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
     }
 
-    if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
-        $context->instanceid   = 0;
-        $context->path         = '/'.$context->id;
-        $context->depth        = 1;
-        $DB->update_record('context', $context);
+    // verify role archetype actually exists
+    $archetypes = get_role_archetypes();
+    if (empty($archetypes[$archetype])) {
+        $archetype = '';
     }
 
-    if (!defined('SYSCONTEXTID')) {
-        define('SYSCONTEXTID', $context->id);
+    // Insert the role record.
+    $role = new stdClass();
+    $role->name        = $name;
+    $role->shortname   = $shortname;
+    $role->description = $description;
+    $role->archetype   = $archetype;
+
+    //find free sortorder number
+    $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
+    if (empty($role->sortorder)) {
+        $role->sortorder = 1;
     }
+    $id = $DB->insert_record('role', $role);
 
-    $ACCESSLIB_PRIVATE->systemcontext = $context;
-    return $ACCESSLIB_PRIVATE->systemcontext;
+    return $id;
 }
 
 /**
- * Remove a context record and any dependent entries,
- * removes context from static context cache too
+ * Function that deletes a role and cleanups up after it
  *
- * @param int $level
- * @param int $instanceid
- * @param bool $deleterecord false means keep record for now
- * @return bool returns true or throws an exception
+ * @param int $roleid id of role to delete
+ * @return bool always true
  */
-function delete_context($contextlevel, $instanceid, $deleterecord = true) {
-    global $DB, $ACCESSLIB_PRIVATE, $CFG;
-
-    // do not use get_context_instance(), because the related object might not exist,
-    // or the context does not exist yet and it would be created now
-    if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
-        // delete these first because they might fetch the context and try to recreate it!
-        blocks_delete_all_for_context($context->id);
-        filter_delete_all_for_context($context->id);
-
-        require_once($CFG->dirroot . '/comment/lib.php');
-        comment::delete_comments(array('contextid'=>$context->id));
+function delete_role($roleid) {
+    global $DB;
 
-        require_once($CFG->dirroot.'/rating/lib.php');
-        $delopt = new stdclass();
-        $delopt->contextid = $context->id;
-        $rm = new rating_manager();
-        $rm->delete_ratings($delopt);
+    // first unssign all users
+    role_unassign_all(array('roleid'=>$roleid));
 
-        // delete all files attached to this context
-        $fs = get_file_storage();
-        $fs->delete_area_files($context->id);
+    // cleanup all references to this role, ignore errors
+    $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
+    $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
+    $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
+    $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
+    $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
+    $DB->delete_records('role_names',          array('roleid'=>$roleid));
+    $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
 
-        // now delete stuff from role related tables, role_unassign_all
-        // and unenrol should be called earlier to do proper cleanup
-        $DB->delete_records('role_assignments', array('contextid'=>$context->id));
-        $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
-        $DB->delete_records('role_names', array('contextid'=>$context->id));
+    // finally delete the role itself
+    // get this before the name is gone for logging
+    $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
 
-        // and finally it is time to delete the context record if requested
-        if ($deleterecord) {
-            $DB->delete_records('context', array('id'=>$context->id));
-            // purge static context cache if entry present
-            $ACCESSLIB_PRIVATE->contexcache->remove($context);
-        }
+    $DB->delete_records('role', array('id'=>$roleid));
 
-        // do not mark dirty contexts if parents unknown
-        if (!is_null($context->path) and $context->depth > 0) {
-            mark_context_dirty($context->path);
-        }
-    }
+    add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
 
     return true;
 }
 
 /**
- * Precreates all contexts including all parents
+ * Function to write context specific overrides, or default capabilities.
  *
- * @param int $contextlevel empty means all
- * @param bool $buildpaths update paths and depths
- * @return void
+ * NOTE: use $context->mark_dirty() after this
+ *
+ * @param string $capability string name
+ * @param int $permission CAP_ constants
+ * @param int $roleid role id
+ * @param int|context $contextid context id
+ * @param bool $overwrite
+ * @return bool always true or exception
  */
-function create_contexts($contextlevel = null, $buildpaths = true) {
-    global $DB;
-
-    //make sure system context exists
-    $syscontext = get_system_context(false);
-
-    if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
-                             or $contextlevel == CONTEXT_COURSE
-                             or $contextlevel == CONTEXT_MODULE
-                             or $contextlevel == CONTEXT_BLOCK) {
-        $sql = "INSERT INTO {context} (contextlevel, instanceid)
-                SELECT ".CONTEXT_COURSECAT.", cc.id
-                  FROM {course}_categories cc
-                 WHERE NOT EXISTS (SELECT 'x'
-                                     FROM {context} cx
-                                    WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
-        $DB->execute($sql);
-
-    }
-
-    if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
-                             or $contextlevel == CONTEXT_MODULE
-                             or $contextlevel == CONTEXT_BLOCK) {
-        $sql = "INSERT INTO {context} (contextlevel, instanceid)
-                SELECT ".CONTEXT_COURSE.", c.id
-                  FROM {course} c
-                 WHERE NOT EXISTS (SELECT 'x'
-                                     FROM {context} cx
-                                    WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
-        $DB->execute($sql);
+function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
+    global $USER, $DB;
 
+    if ($contextid instanceof context) {
+        $context = $contextid;
+    } else {
+        $context = context::instance_by_id($contextid);
     }
 
-    if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
-                             or $contextlevel == CONTEXT_BLOCK) {
-        $sql = "INSERT INTO {context} (contextlevel, instanceid)
-                SELECT ".CONTEXT_MODULE.", cm.id
-                  FROM {course}_modules cm
-                 WHERE NOT EXISTS (SELECT 'x'
-                                     FROM {context} cx
-                                    WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
-        $DB->execute($sql);
+    if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
+        unassign_capability($capability, $roleid, $context->id);
+        return true;
     }
 
-    if (empty($contextlevel) or $contextlevel == CONTEXT_USER
-                             or $contextlevel == CONTEXT_BLOCK) {
-        $sql = "INSERT INTO {context} (contextlevel, instanceid)
-                SELECT ".CONTEXT_USER.", u.id
-                  FROM {user} u
-                 WHERE u.deleted=0
-                   AND NOT EXISTS (SELECT 'x'
-                                     FROM {context} cx
-                                    WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
-        $DB->execute($sql);
+    $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
 
+    if ($existing and !$overwrite) {   // We want to keep whatever is there already
+        return true;
     }
 
-    if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
-        $sql = "INSERT INTO {context} (contextlevel, instanceid)
-                SELECT ".CONTEXT_BLOCK.", bi.id
-                  FROM {block_instances} bi
-                 WHERE NOT EXISTS (SELECT 'x'
-                                     FROM {context} cx
-                                    WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
-        $DB->execute($sql);
-    }
+    $cap = new stdClass();
+    $cap->contextid    = $context->id;
+    $cap->roleid       = $roleid;
+    $cap->capability   = $capability;
+    $cap->permission   = $permission;
+    $cap->timemodified = time();
+    $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
 
-    if ($buildpaths) {
-        build_context_path(false);
+    if ($existing) {
+        $cap->id = $existing->id;
+        $DB->update_record('role_capabilities', $cap);
+    } else {
+        if ($DB->record_exists('context', array('id'=>$context->id))) {
+            $DB->insert_record('role_capabilities', $cap);
+        }
     }
+    return true;
 }
 
 /**
- * Remove stale context records
+ * Unassign a capability from a role.
  *
- * @return bool
+ * NOTE: use $context->mark_dirty() after this
+ *
+ * @param string $capability the name of the capability
+ * @param int $roleid the role id
+ * @param int|context $contextid null means all contexts
+ * @return boolean true or exception
  */
-function cleanup_contexts() {
+function unassign_capability($capability, $roleid, $contextid = null) {
     global $DB;
 
-    $sql = "  SELECT c.contextlevel,
-                     c.instanceid AS instanceid
-                FROM {context} c
-                LEFT OUTER JOIN {course}_categories t
-                     ON c.instanceid = t.id
-               WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
-            UNION
-              SELECT c.contextlevel,
-                     c.instanceid
-                FROM {context} c
-                LEFT OUTER JOIN {course} t
-                     ON c.instanceid = t.id
-               WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
-            UNION
-              SELECT c.contextlevel,
-                     c.instanceid
-                FROM {context} c
-                LEFT OUTER JOIN {course}_modules t
-                     ON c.instanceid = t.id
-               WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
-            UNION
-              SELECT c.contextlevel,
-                     c.instanceid
-                FROM {context} c
-                LEFT OUTER JOIN {user} t
-                     ON c.instanceid = t.id
-               WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
-            UNION
-              SELECT c.contextlevel,
-                     c.instanceid
-                FROM {context} c
-                LEFT OUTER JOIN {block_instances} t
-                     ON c.instanceid = t.id
-               WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
-           ";
-
-    // transactions used only for performance reasons here
-    $transaction = $DB->start_delegated_transaction();
-
-    $rs = $DB->get_recordset_sql($sql);
-    foreach ($rs as $ctx) {
-        delete_context($ctx->contextlevel, $ctx->instanceid);
+    if (!empty($contextid)) {
+        if ($contextid instanceof context) {
+            $context = $contextid;
+        } else {
+            $context = context::instance_by_id($contextid);
+        }
+        // delete from context rel, if this is the last override in this context
+        $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
+    } else {
+        $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
     }
-    $rs->close();
-
-    $transaction->allow_commit();
     return true;
 }
 
 /**
- * Preloads all contexts relating to a course: course, modules. Block contexts
- * are no longer loaded here. The contexts for all the blocks on the current
- * page are now efficiently loaded by {@link block_manager::load_blocks()}.
+ * Get the roles that have a given capability assigned to it
  *
- * @param int $courseid Course ID
- * @return void
+ * This function does not resolve the actual permission of the capability.
+ * It just checks for permissions and overrides.
+ * Use get_roles_with_cap_in_context() if resolution is required.
+ *
+ * @param string $capability - capability name (string)
+ * @param string $permission - optional, the permission defined for this capability
+ *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
+ * @param stdClass $context, null means any
+ * @return array of role records
  */
-function preload_course_contexts($courseid) {
-    global $DB, $ACCESSLIB_PRIVATE;
+function get_roles_with_capability($capability, $permission = null, $context = null) {
+    global $DB;
 
-    // Users can call this multiple times without doing any harm
-    global $ACCESSLIB_PRIVATE;
-    if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
-        return;
+    if ($context) {
+        $contexts = $context->get_parent_context_ids(true);
+        list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
+        $contextsql = "AND rc.contextid $insql";
+    } else {
+        $params = array();
+        $contextsql = '';
     }
 
-    $params = array($courseid, $courseid, $courseid);
-    $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
-              FROM {course_modules} cm
-              JOIN {context} x ON x.instanceid=cm.id
-             WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
-
-         UNION ALL
-
-            SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
-              FROM {context} x
-             WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
-
-    $rs = $DB->get_recordset_sql($sql, $params);
-    foreach($rs as $context) {
-        $ACCESSLIB_PRIVATE->contexcache->add($context);
+    if ($permission) {
+        $permissionsql = " AND rc.permission = :permission";
+        $params['permission'] = $permission;
+    } else {
+        $permissionsql = '';
     }
-    $rs->close();
-    $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
+
+    $sql = "SELECT r.*
+              FROM {role} r
+             WHERE r.id IN (SELECT rc.roleid
+                              FROM {role_capabilities} rc
+                             WHERE rc.capability = :capname
+                                   $contextsql
+                                   $permissionsql)";
+    $params['capname'] = $capability;
+
+
+    return $DB->get_records_sql($sql, $params);
 }
 
+
 /**
- * Get the context instance as an object. This function will create the
- * context instance if it does not exist yet.
- *
- * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
+ * This function makes a role-assignment (a role for a user in a particular context)
  *
- * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
- * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
- *      for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
- * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
- *      MUST_EXIST means throw exception if no record or multiple records found
- * @return object The context object.
+ * @param int $roleid the role of the id
+ * @param int $userid userid
+ * @param int|context $contextid id of the context
+ * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
+ * @param int $itemid id of enrolment/auth plugin
+ * @param string $timemodified defaults to current time
+ * @return int new/existing id of the assignment
  */
-function get_context_instance($contextlevel, $instance = 0, $strictness = IGNORE_MISSING) {
-    global $DB, $ACCESSLIB_PRIVATE;
-    static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
+function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
+    global $USER, $DB;
 
-/// System context has special cache
-    if ($contextlevel == CONTEXT_SYSTEM) {
-        return get_system_context();
+    // first of all detect if somebody is using old style parameters
+    if ($contextid === 0 or is_numeric($component)) {
+        throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
     }
 
-/// check allowed context levels
-    if (!in_array($contextlevel, $allowed_contexts)) {
-        // fatal error, code must be fixed - probably typo or switched parameters
-        print_error('invalidcourselevel');
+    // now validate all parameters
+    if (empty($roleid)) {
+        throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
     }
 
-    // Various operations rely on context cache
-    $cache = $ACCESSLIB_PRIVATE->contexcache;
+    if (empty($userid)) {
+        throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
+    }
 
-    if (!is_array($instance)) {
-    /// Check the cache
-        $context = $cache->get($contextlevel, $instance);
-        if ($context) {
-            return $context;
+    if ($itemid) {
+        if (strpos($component, '_') === false) {
+            throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
         }
-
-    /// Get it from the database, or create it
-        if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
-            $context = create_context($contextlevel, $instance, $strictness);
+    } else {
+        $itemid = 0;
+        if ($component !== '' and strpos($component, '_') === false) {
+            throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
         }
+    }
 
-    /// Only add to cache if context isn't empty.
-        if (!empty($context)) {
-            $cache->add($context);
-        }
+    if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
+        throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
+    }
 
-        return $context;
+    if ($contextid instanceof context) {
+        $context = $contextid;
+    } else {
+        $context = context::instance_by_id($contextid, MUST_EXIST);
     }
 
+    if (!$timemodified) {
+        $timemodified = time();
+    }
 
-/// ok, somebody wants to load several contexts to save some db queries ;-)
-    $instances = $instance;
-    $result = array();
+/// Check for existing entry
+    $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
 
-    foreach ($instances as $key=>$instance) {
-    /// Check the cache first
-        if ($context = $cache->get($contextlevel, $instance)) {  // Already cached
-            $result[$instance] = $context;
-            unset($instances[$key]);
-            continue;
+    if ($ras) {
+        // role already assigned - this should not happen
+        if (count($ras) > 1) {
+            // very weird - remove all duplicates!
+            $ra = array_shift($ras);
+            foreach ($ras as $r) {
+                $DB->delete_records('role_assignments', array('id'=>$r->id));
+            }
+        } else {
+            $ra = reset($ras);
         }
-    }
 
-    if ($instances) {
-        list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
-        array_unshift($params, $contextlevel);
-        $sql = "SELECT instanceid, id, contextlevel, path, depth
-                  FROM {context}
-                 WHERE contextlevel=? AND instanceid $instanceids";
+        // actually there is no need to update, reset anything or trigger any event, so just return
+        return $ra->id;
+    }
 
-        if (!$contexts = $DB->get_records_sql($sql, $params)) {
-            $contexts = array();
-        }
+    // Create a new entry
+    $ra = new stdClass();
+    $ra->roleid       = $roleid;
+    $ra->contextid    = $context->id;
+    $ra->userid       = $userid;
+    $ra->component    = $component;
+    $ra->itemid       = $itemid;
+    $ra->timemodified = $timemodified;
+    $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
 
-        foreach ($instances as $instance) {
-            if (isset($contexts[$instance])) {
-                $context = $contexts[$instance];
-            } else {
-                $context = create_context($contextlevel, $instance);
-            }
+    $ra->id = $DB->insert_record('role_assignments', $ra);
 
-            if (!empty($context)) {
-                $cache->add($context);
-            }
+    // mark context as dirty - again expensive, but needed
+    $context->mark_dirty();
 
-            $result[$instance] = $context;
-        }
+    if (!empty($USER->id) && $USER->id == $userid) {
+        // If the user is the current user, then do full reload of capabilities too.
+        reload_all_capabilities();
     }
 
-    return $result;
-}
+    events_trigger('role_assigned', $ra);
 
+    return $ra->id;
+}
 
 /**
- * Get a context instance as an object, from a given context id.
+ * Removes one role assignment
  *
- * @param int $id context id
- * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
- *                        MUST_EXIST means throw exception if no record or multiple records found
- * @return stdClass|bool the context object or false if not found.
+ * @param int $roleid
+ * @param int  $userid
+ * @param int|context  $contextid
+ * @param string $component
+ * @param int  $itemid
+ * @return void
  */
-function get_context_instance_by_id($id, $strictness = IGNORE_MISSING) {
-    global $DB, $ACCESSLIB_PRIVATE;
-
-    if ($id == SYSCONTEXTID) {
-        return get_system_context();
-    }
-
-    $cache = $ACCESSLIB_PRIVATE->contexcache;
-    if ($context = $cache->get_by_id($id)) {
-        return $context;
+function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
+    // first make sure the params make sense
+    if ($roleid == 0 or $userid == 0 or $contextid == 0) {
+        throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
     }
 
-    if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
-        $cache->add($context);
-        return $context;
+    if ($itemid) {
+        if (strpos($component, '_') === false) {
+            throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
+        }
+    } else {
+        $itemid = 0;
+        if ($component !== '' and strpos($component, '_') === false) {
+            throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
+        }
     }
 
-    return false;
+    role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
 }
 
-
 /**
- * Get the local override (if any) for a given capability in a role in a context
+ * Removes multiple role assignments, parameters may contain:
+ *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
  *
- * @param int $roleid
- * @param int $contextid
- * @param string $capability
- */
-function get_local_override($roleid, $contextid, $capability) {
-    global $DB;
-    return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
-}
-
-/**
- * Returns context instance plus related course and cm instances
- * @param int $contextid
- * @return array of ($context, $course, $cm)
+ * @param array $params role assignment parameters
+ * @param bool $subcontexts unassign in subcontexts too
+ * @param bool $includemanual include manual role assignments too
+ * @return void
  */
-function get_context_info_array($contextid) {
-    global $DB;
-
-    $context = get_context_instance_by_id($contextid, MUST_EXIST);
-    $course  = null;
-    $cm      = null;
-
-    if ($context->contextlevel == CONTEXT_COURSE) {
-        $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
-
-    } else if ($context->contextlevel == CONTEXT_MODULE) {
-        $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
-        $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
+    global $USER, $CFG, $DB;
 
-    } else if ($context->contextlevel == CONTEXT_BLOCK) {
-        $parentcontexts = get_parent_contexts($context, false);
-        $parent = reset($parentcontexts);
-        $parent = get_context_instance_by_id($parent);
+    if (!$params) {
+        throw new coding_exception('Missing parameters in role_unsassign_all() call');
+    }
 
-        if ($parent->contextlevel == CONTEXT_COURSE) {
-            $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
-        } else if ($parent->contextlevel == CONTEXT_MODULE) {
-            $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
-            $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+    $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
+    foreach ($params as $key=>$value) {
+        if (!in_array($key, $allowed)) {
+            throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
         }
     }
 
-    return array($context, $course, $cm);
-}
-
-/**
- * Returns current course id or null if outside of course based on context parameter.
- * @param object $context
- * @return int|bool related course id or false
- */
-function get_courseid_from_context($context) {
-    if (empty($context->contextlevel)) {
-        debugging('Invalid context object specified in get_courseid_from_context() call');
-        return false;
+    if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
+        throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
     }
-    if ($context->contextlevel == CONTEXT_COURSE) {
-        return $context->instanceid;
+
+    if ($includemanual) {
+        if (!isset($params['component']) or $params['component'] === '') {
+            throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
+        }
     }
 
-    if ($context->contextlevel < CONTEXT_COURSE) {
-        return false;
+    if ($subcontexts) {
+        if (empty($params['contextid'])) {
+            throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
+        }
     }
 
-    if ($context->contextlevel == CONTEXT_MODULE) {
-        $parentcontexts = get_parent_contexts($context, false);
-        $parent = reset($parentcontexts);
-        $parent = get_context_instance_by_id($parent);
-        return $parent->instanceid;
+    $ras = $DB->get_records('role_assignments', $params);
+    foreach($ras as $ra) {
+        $DB->delete_records('role_assignments', array('id'=>$ra->id));
+        if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
+            // this is a bit expensive but necessary
+            $context->mark_dirty();
+            /// If the user is the current user, then do full reload of capabilities too.
+            if (!empty($USER->id) && $USER->id == $ra->userid) {
+                reload_all_capabilities();
+            }
+        }
+        events_trigger('role_unassigned', $ra);
     }
+    unset($ras);
+
+    // process subcontexts
+    if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
+        if ($params['contextid'] instanceof context) {
+            $context = $params['contextid'];
+        } else {
+            $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
+        }
 
-    if ($context->contextlevel == CONTEXT_BLOCK) {
-        $parentcontexts = get_parent_contexts($context, false);
-        $parent = reset($parentcontexts);
-        return get_courseid_from_context(get_context_instance_by_id($parent));
+        if ($context) {
+            $contexts = $context->get_child_contexts();
+            $mparams = $params;
+            foreach($contexts as $context) {
+                $mparams['contextid'] = $context->id;
+                $ras = $DB->get_records('role_assignments', $mparams);
+                foreach($ras as $ra) {
+                    $DB->delete_records('role_assignments', array('id'=>$ra->id));
+                    // this is a bit expensive but necessary
+                    $context->mark_dirty();
+                    /// If the user is the current user, then do full reload of capabilities too.
+                    if (!empty($USER->id) && $USER->id == $ra->userid) {
+                        reload_all_capabilities();
+                    }
+                    events_trigger('role_unassigned', $ra);
+                }
+            }
+        }
     }
 
-    return false;
+    // do this once more for all manual role assignments
+    if ($includemanual) {
+        $params['component'] = '';
+        role_unassign_all($params, $subcontexts, false);
+    }
 }
 
+/**
+ * Determines if a user is currently logged in
+ *
+ * @return bool
+ */
+function isloggedin() {
+    global $USER;
 
-//////////////////////////////////////
-//    DB TABLE RELATED FUNCTIONS    //
-//////////////////////////////////////
+    return (!empty($USER->id));
+}
 
 /**
- * function that creates a role
+ * Determines if a user is logged in as real guest user with username 'guest'.
  *
- * @param string $name role name
- * @param string $shortname role short name
- * @param string $description role description
- * @param string $archetype
- * @return int id or dml_exception
+ * @param int|object $user mixed user object or id, $USER if not specified
+ * @return bool true if user is the real guest user, false if not logged in or other user
  */
-function create_role($name, $shortname, $description, $archetype = '') {
-    global $DB;
+function isguestuser($user = null) {
+    global $USER, $DB, $CFG;
 
-    if (strpos($archetype, 'moodle/legacy:') !== false) {
-        throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
+    // make sure we have the user id cached in config table, because we are going to use it a lot
+    if (empty($CFG->siteguest)) {
+        if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
+            // guest does not exist yet, weird
+            return false;
+        }
+        set_config('siteguest', $guestid);
     }
-
-    // verify role archetype actually exists
-    $archetypes = get_role_archetypes();
-    if (empty($archetypes[$archetype])) {
-        $archetype = '';
+    if ($user === null) {
+        $user = $USER;
     }
 
-    // Get the system context.
-    $context = get_context_instance(CONTEXT_SYSTEM);
+    if ($user === null) {
+        // happens when setting the $USER
+        return false;
 
-    // Insert the role record.
-    $role = new stdClass();
-    $role->name        = $name;
-    $role->shortname   = $shortname;
-    $role->description = $description;
-    $role->archetype   = $archetype;
+    } else if (is_numeric($user)) {
+        return ($CFG->siteguest == $user);
 
-    //find free sortorder number
-    $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
-    if (empty($role->sortorder)) {
-        $role->sortorder = 1;
-    }
-    $id = $DB->insert_record('role', $role);
+    } else if (is_object($user)) {
+        if (empty($user->id)) {
+            return false; // not logged in means is not be guest
+        } else {
+            return ($CFG->siteguest == $user->id);
+        }
 
-    return $id;
+    } else {
+        throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
+    }
 }
 
 /**
- * Function that deletes a role and cleanups up after it
+ * Does user have a (temporary or real) guest access to course?
  *
- * @param int $roleid id of role to delete
- * @return bool always true
+ * @param context $context
+ * @param stdClass|int $user
+ * @return bool
  */
-function delete_role($roleid) {
-    global $CFG, $DB;
+function is_guest(context $context, $user = null) {
+    global $USER;
 
-    // first unssign all users
-    role_unassign_all(array('roleid'=>$roleid));
+    // first find the course context
+    $coursecontext = $context->get_course_context();
 
-    // cleanup all references to this role, ignore errors
-    $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
-    $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
-    $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
-    $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
-    $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
-    $DB->delete_records('role_names',          array('roleid'=>$roleid));
-    $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
+    // make sure there is a real user specified
+    if ($user === null) {
+        $userid = isset($USER->id) ? $USER->id : 0;
+    } else {
+        $userid = is_object($user) ? $user->id : $user;
+    }
 
-    // finally delete the role itself
-    // get this before the name is gone for logging
-    $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
+    if (isguestuser($userid)) {
+        // can not inspect or be enrolled
+        return true;
+    }
 
-    $DB->delete_records('role', array('id'=>$roleid));
+    if (has_capability('moodle/course:view', $coursecontext, $user)) {
+        // viewing users appear out of nowhere, they are neither guests nor participants
+        return false;
+    }
 
-    add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
+    // consider only real active enrolments here
+    if (is_enrolled($coursecontext, $user, '', true)) {
+        return false;
+    }
 
     return true;
 }
 
 /**
- * Function to write context specific overrides, or default capabilities.
+ * Returns true if the user has moodle/course:view capability in the course,
+ * this is intended for admins, managers (aka small admins), inspectors, etc.
  *
- * @param string $capability string name
- * @param int $permission CAP_ constants
- * @param int $roleid role id
- * @param int $contextid context id
- * @param bool $overwrite
- * @return bool always true or exception
+ * @param context $context
+ * @param int|stdClass $user, if null $USER is used
+ * @param string $withcapability extra capability name
+ * @return bool
  */
-function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
-    global $USER, $DB;
+function is_viewing(context $context, $user = null, $withcapability = '') {
+    // first find the course context
+    $coursecontext = $context->get_course_context();
 
-    if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
-        unassign_capability($capability, $roleid, $contextid);
-        return true;
+    if (isguestuser($user)) {
+        // can not inspect
+        return false;
     }
 
-    $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
-
-    if ($existing and !$overwrite) {   // We want to keep whatever is there already
-        return true;
+    if (!has_capability('moodle/course:view', $coursecontext, $user)) {
+        // admins are allowed to inspect courses
+        return false;
     }
 
-    $cap = new stdClass();
-    $cap->contextid    = $contextid;
-    $cap->roleid       = $roleid;
-    $cap->capability   = $capability;
-    $cap->permission   = $permission;
-    $cap->timemodified = time();
-    $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
-
-    if ($existing) {
-        $cap->id = $existing->id;
-        $DB->update_record('role_capabilities', $cap);
-    } else {
-        $c = $DB->get_record('context', array('id'=>$contextid));
-        $DB->insert_record('role_capabilities', $cap);
+    if ($withcapability and !has_capability($withcapability, $context, $user)) {
+        // site admins always have the capability, but the enrolment above blocks
+        return false;
     }
+
     return true;
 }
 
 /**
- * Unassign a capability from a role.
+ * Returns true if user is enrolled (is participating) in course
+ * this is intended for students and teachers.
  *
- * @param string $capability the name of the capability
- * @param int $roleid the role id
- * @param int $contextid null means all contexts
- * @return boolean success or failure
+ * @param context $context
+ * @param int|stdClass $user, if null $USER is used, otherwise user object or id expected
+ * @param string $withcapability extra capability name
+ * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
+ * @return bool
  */
-function unassign_capability($capability, $roleid, $contextid = null) {
-    global $DB;
+function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
+    global $USER, $DB;
 
-    if (!empty($contextid)) {
-        // delete from context rel, if this is the last override in this context
-        $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
+    // first find the course context
+    $coursecontext = $context->get_course_context();
+
+    // make sure there is a real user specified
+    if ($user === null) {
+        $userid = isset($USER->id) ? $USER->id : 0;
     } else {
-        $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
+        $userid = is_object($user) ? $user->id : $user;
     }
-    return true;
-}
-
-
-/**
- * Get the roles that have a given capability assigned to it
- *
- * This function does not resolve the actual permission of the capability.
- * It just checks for permissions and overrides.
- * Use get_roles_with_cap_in_context() if resolution is required.
- *
- * @param string $capability - capability name (string)
- * @param string $permission - optional, the permission defined for this capability
- *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
- * @param stdClass $context, null means any
- * @return array of role objects
- */
-function get_roles_with_capability($capability, $permission = null, $context = null) {
-    global $DB;
 
-    if ($context) {
-        $contexts = get_parent_contexts($context, true);
-        list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
-        $contextsql = "AND rc.contextid $insql";
-    } else {
-        $params = array();
-        $contextsql = '';
+    if (empty($userid)) {
+        // not-logged-in!
+        return false;
+    } else if (isguestuser($userid)) {
+        // guest account can not be enrolled anywhere
+        return false;
     }
 
-    if ($permission) {
-        $permissionsql = " AND rc.permission = :permission";
-        $params['permission'] = $permission;
+    if ($coursecontext->instanceid == SITEID) {
+        // everybody participates on frontpage
     } else {
-        $permissionsql = '';
-    }
+        if ($onlyactive) {
+            $sql = "SELECT ue.*
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
+                      JOIN {user} u ON u.id = ue.userid
+                     WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
+            $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
+            // this result should be very small, better not do the complex time checks in sql for now ;-)
+            $enrolments = $DB->get_records_sql($sql, $params);
+            $now = time();
+            // make sure the enrol period is ok
+            $result = false;
+            foreach ($enrolments as $e) {
+                if ($e->timestart > $now) {
+                    continue;
+                }
+                if ($e->timeend and $e->timeend < $now) {
+                    continue;
+                }
+                $result = true;
+                break;
+            }
+            if (!$result) {
+                return false;
+            }
 
-    $sql = "SELECT r.*
-              FROM {role} r
-             WHERE r.id IN (SELECT rc.roleid
-                              FROM {role_capabilities} rc
-                             WHERE rc.capability = :capname
-                                   $contextsql
-                                   $permissionsql)";
-    $params['capname'] = $capability;
+        } else {
+            // any enrolment is good for us here, even outdated, disabled or inactive
+            $sql = "SELECT 'x'
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
+                      JOIN {user} u ON u.id = ue.userid
+                     WHERE ue.userid = :userid AND u.deleted = 0";
+            $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
+            if (!$DB->record_exists_sql($sql, $params)) {
+                return false;
+            }
+        }
+    }
 
+    if ($withcapability and !has_capability($withcapability, $context, $userid)) {
+        return false;
+    }
 
-    return $DB->get_records_sql($sql, $params);
+    return true;
 }
 
-
 /**
- * This function makes a role-assignment (a role for a user in a particular context)
+ * Returns true if the user is able to access the course.
  *
- * @param int $roleid the role of the id
- * @param int $userid userid
- * @param int $contextid id of the context
- * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
- * @prama int $itemid id of enrolment/auth plugin
- * @param string $timemodified defaults to current time
- * @return int new/existing id of the assignment
- */
-function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
-    global $USER, $CFG, $DB;
-
-    // first of all detect if somebody is using old style parameters
-    if ($contextid === 0 or is_numeric($component)) {
-        throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
-    }
-
-    // now validate all parameters
-    if (empty($roleid)) {
-        throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
-    }
-
-    if (empty($userid)) {
-        throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
-    }
+ * This function is in no way, shape, or form a substitute for require_login.
+ * It should only be used in circumstances where it is not possible to call require_login
+ * such as the navigation.
+ *
+ * This function checks many of the methods of access to a course such as the view
+ * capability, enrollments, and guest access. It also makes use of the cache
+ * generated by require_login for guest access.
+ *
+ * The flags within the $USER object that are used here should NEVER be used outside
+ * of this function can_access_course and require_login. Doing so WILL break future
+ * versions.
+ *
+ * @param context $context
+ * @param stdClass|null $user
+ * @param string $withcapability Check for this capability as well.
+ * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
+ * @param boolean $trustcache If set to false guest access will always be checked
+ *                             against the enrolment plugins from the course, rather
+ *                             than the cache generated by require_login.
+ * @return boolean Returns true if the user is able to access the course
+ */
+function can_access_course(context $context, $user = null, $withcapability = '', $onlyactive = false, $trustcache = true) {
+    global $DB, $USER;
 
-    if ($itemid) {
-        if (strpos($component, '_') === false) {
-            throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
-        }
-    } else {
-        $itemid = 0;
-        if ($component !== '' and strpos($component, '_') === false) {
-            throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
-        }
-    }
+    $coursecontext = $context->get_course_context();
+    $courseid = $coursecontext->instanceid;
 
-    if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
-        throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
+    // First check the obvious, is the user viewing or is the user enrolled.
+    if (is_viewing($coursecontext, $user, $withcapability) || is_enrolled($coursecontext, $user, $withcapability, $onlyactive)) {
+        // How easy was that!
+        return true;
     }
 
-    $context = get_context_instance_by_id($contextid, MUST_EXIST);
-
-    if (!$timemodified) {
-        $timemodified = time();
+    $access = false;
+    if (!isset($USER->enrol)) {
+        // Cache hasn't been generated yet so we can't trust it
+        $trustcache = false;
+        /**
+         * These flags within the $USER object should NEVER be used outside of this
+         * function can_access_course and the function require_login.
+         * Doing so WILL break future versions!!!!
+         */
+        $USER->enrol = array();
+        $USER->enrol['enrolled'] = array();
+        $USER->enrol['tempguest'] = array();
     }
 
-/// Check for existing entry
-    $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
-
-    if ($ras) {
-        // role already assigned - this should not happen
-        if (count($ras) > 1) {
-            //very weird - remove all duplicates!
-            $ra = array_shift($ras);
-            foreach ($ras as $r) {
-                $DB->delete_records('role_assignments', array('id'=>$r->id));
+    // If we don't trust the cache we need to check with the courses enrolment
+    // plugin instances to see if the user can access the course as a guest.
+    if (!$trustcache) {
+        // Ok, off to the database we go!
+        $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
+        $enrols = enrol_get_plugins(true);
+        foreach($instances as $instance) {
+            if (!isset($enrols[$instance->enrol])) {
+                continue;
+            }
+            $until = $enrols[$instance->enrol]->try_guestaccess($instance);
+            if ($until !== false) {
+                // Never use me anywhere but here and require_login
+                $USER->enrol['tempguest'][$courseid] = $until;
+                $access = true;
+                break;
             }
-        } else {
-            $ra = reset($ras);
         }
-
-        // actually there is no need to update, reset anything or trigger any event, so just return
-        return $ra->id;
     }
 
-    // Create a new entry
-    $ra = new stdClass();
-    $ra->roleid       = $roleid;
-    $ra->contextid    = $context->id;
-    $ra->userid       = $userid;
-    $ra->component    = $component;
-    $ra->itemid       = $itemid;
-    $ra->timemodified = $timemodified;
-    $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
-
-    $ra->id = $DB->insert_record('role_assignments', $ra);
-
-    // mark context as dirty - again expensive, but needed
-    mark_context_dirty($context->path);
-
-    if (!empty($USER->id) && $USER->id == $userid) {
-        // If the user is the current user, then do full reload of capabilities too.
-        load_all_capabilities();
+    // If we don't already have access (from above) check the cache and see whether
+    // there is record of it in there.
+    if (!$access && isset($USER->enrol['tempguest'][$courseid])) {
+        // Never use me anywhere but here and require_login
+        if ($USER->enrol['tempguest'][$courseid] == 0) {
+            $access = true;
+        } else if ($USER->enrol['tempguest'][$courseid] > time()) {
+            $access = true;
+        } else {
+            //expired
+            unset($USER->enrol['tempguest'][$courseid]);
+        }
     }
-
-    events_trigger('role_assigned', $ra);
-
-    return $ra->id;
+    return $access;
 }
 
 /**
- * Removes one role assignment
+ * Returns array with sql code and parameters returning all ids
+ * of users enrolled into course.
  *
- * @param int $roleid
- * @param int  $userid
- * @param int  $contextid
- * @param string $component
- * @param int  $itemid
- * @return void
+ * This function is using 'eu[0-9]+_' prefix for table names and parameters.
+ *
+ * @param context $context
+ * @param string $withcapability
+ * @param int $groupid 0 means ignore groups, any other value limits the result by group id
+ * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
+ * @return array list($sql, $params)
  */
-function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
-    global $USER, $CFG, $DB;
-
-    // first make sure the params make sense
-    if ($roleid == 0 or $userid == 0 or $contextid == 0) {
-        throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
-    }
-
-    if ($itemid) {
-        if (strpos($component, '_') === false) {
-            throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
-        }
-    } else {
-        $itemid = 0;
-        if ($component !== '' and strpos($component, '_') === false) {
-            throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
-        }
-    }
+function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
+    global $DB, $CFG;
 
-    role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
-}
+    // use unique prefix just in case somebody makes some SQL magic with the result
+    static $i = 0;
+    $i++;
+    $prefix = 'eu'.$i.'_';
 
-/**
- * Removes multiple role assignments, parameters may contain:
- *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
- *
- * @param array $params role assignment parameters
- * @param bool $subcontexts unassign in subcontexts too
- * @param bool $includmanual include manual role assignments too
- * @return void
- */
-function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
-    global $USER, $CFG, $DB;
+    // first find the course context
+    $coursecontext = $context->get_course_context();
 
-    if (!$params) {
-        throw new coding_exception('Missing parameters in role_unsassign_all() call');
-    }
+    $isfrontpage = ($coursecontext->instanceid == SITEID);
 
-    $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
-    foreach ($params as $key=>$value) {
-        if (!in_array($key, $allowed)) {
-            throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
-        }
-    }
+    $joins  = array();
+    $wheres = array();
+    $params = array();
 
-    if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
-        throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
-    }
+    list($contextids, $contextpaths) = get_context_info_list($context);
 
-    if ($includemanual) {
-        if (!isset($params['component']) or $params['component'] === '') {
-            throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
-        }
-    }
+    // get all relevant capability info for all roles
+    if ($withcapability) {
+        list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx');
+        $cparams['cap'] = $withcapability;
 
-    if ($subcontexts) {
-        if (empty($params['contextid'])) {
-            throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
+        $defs = array();
+        $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
+                  FROM {role_capabilities} rc
+                  JOIN {context} ctx on rc.contextid = ctx.id
+                 WHERE rc.contextid $incontexts AND rc.capability = :cap";
+        $rcs = $DB->get_records_sql($sql, $cparams);
+        foreach ($rcs as $rc) {
+            $defs[$rc->path][$rc->roleid] = $rc->permission;
         }
-    }
 
-    $ras = $DB->get_records('role_assignments', $params);
-    foreach($ras as $ra) {
-        $DB->delete_records('role_assignments', array('id'=>$ra->id));
-        if ($context = get_context_instance_by_id($ra->contextid)) {
-            // this is a bit expensive but necessary
-            mark_context_dirty($context->path);
-            /// If the user is the current user, then do full reload of capabilities too.
-            if (!empty($USER->id) && $USER->id == $ra->userid) {
-                load_all_capabilities();
+        $access = array();
+        if (!empty($defs)) {
+            foreach ($contextpaths as $path) {
+                if (empty($defs[$path])) {
+                    continue;
+                }
+                foreach($defs[$path] as $roleid => $perm) {
+                    if ($perm == CAP_PROHIBIT) {
+                        $access[$roleid] = CAP_PROHIBIT;
+                        continue;
+                    }
+                    if (!isset($access[$roleid])) {
+                        $access[$roleid] = (int)$perm;
+                    }
+                }
             }
         }
-        events_trigger('role_unassigned', $ra);
-    }
-    unset($ras);
 
-    // process subcontexts
-    if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) {
-        $contexts = get_child_contexts($context);
-        $mparams = $params;
-        foreach($contexts as $context) {
-            $mparams['contextid'] = $context->id;
-            $ras = $DB->get_records('role_assignments', $mparams);
-            foreach($ras as $ra) {
-                $DB->delete_records('role_assignments', array('id'=>$ra->id));
-                // this is a bit expensive but necessary
-                mark_context_dirty($context->path);
-                /// If the user is the current user, then do full reload of capabilities too.
-                if (!empty($USER->id) && $USER->id == $ra->userid) {
-                    load_all_capabilities();
-                }
-                events_trigger('role_unassigned', $ra);
+        unset($defs);
+
+        // make lists of roles that are needed and prohibited
+        $needed     = array(); // one of these is enough
+        $prohibited = array(); // must not have any of these
+        foreach ($access as $roleid => $perm) {
+            if ($perm == CAP_PROHIBIT) {
+                unset($needed[$roleid]);
+                $prohibited[$roleid] = true;
+            } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
+                $needed[$roleid] = true;
             }
         }
-    }
 
-    // do this once more for all manual role assignments
-    if ($includemanual) {
-        $params['component'] = '';
-        role_unassign_all($params, $subcontexts, false);
-    }
-}
+        $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
+        $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
 
+        $nobody = false;
 
-/**
- * Determines if a user is currently logged in
- *
- * @return bool
- */
-function isloggedin() {
-    global $USER;
+        if ($isfrontpage) {
+            if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
+                $nobody = true;
+            } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
+                // everybody not having prohibit has the capability
+                $needed = array();
+            } else if (empty($needed)) {
+                $nobody = true;
+            }
+        } else {
+            if (!empty($prohibited[$defaultuserroleid])) {
+                $nobody = true;
+            } else if (!empty($needed[$defaultuserroleid])) {
+                // everybody not having prohibit has the capability
+                $needed = array();
+            } else if (empty($needed)) {
+                $nobody = true;
+            }
+        }
 
-    return (!empty($USER->id));
-}
+        if ($nobody) {
+            // nobody can match so return some SQL that does not return any results
+            $wheres[] = "1 = 2";
 
-/**
- * Determines if a user is logged in as real guest user with username 'guest'.
- *
- * @param int|object $user mixed user object or id, $USER if not specified
- * @return bool true if user is the real guest user, false if not logged in or other user
- */
-function isguestuser($user = null) {
-    global $USER, $DB, $CFG;
+        } else {
 
-    // make sure we have the user id cached in config table, because we are going to use it a lot
-    if (empty($CFG->siteguest)) {
-        if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
-            // guest does not exist yet, weird
-            return false;
-        }
-        set_config('siteguest', $guestid);
-    }
-    if ($user === null) {
-        $user = $USER;
-    }
+            if ($needed) {
+                $ctxids = implode(',', $contextids);
+                $roleids = implode(',', array_keys($needed));
+                $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))";
+            }
 
-    if ($user === null) {
-        // happens when setting the $USER
-        return false;
+            if ($prohibited) {
+                $ctxids = implode(',', $contextids);
+                $roleids = implode(',', array_keys($prohibited));
+                $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))";
+                $wheres[] = "{$prefix}ra4.id IS NULL";
+            }
 
-    } else if (is_numeric($user)) {
-        return ($CFG->siteguest == $user);
+            if ($groupid) {
+                $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
+                $params["{$prefix}gmid"] = $groupid;
+            }
+        }
 
-    } else if (is_object($user)) {
-        if (empty($user->id)) {
-            return false; // not logged in means is not be guest
-        } else {
-            return ($CFG->siteguest == $user->id);
+    } else {
+        if ($groupid) {
+            $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
+            $params["{$prefix}gmid"] = $groupid;
         }
+    }
 
+    $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
+    $params["{$prefix}guestid"] = $CFG->siteguest;
+
+    if ($isfrontpage) {
+        // all users are "enrolled" on the frontpage
     } else {
-        throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
+        $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
+        $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
+        $params[$prefix.'courseid'] = $coursecontext->instanceid;
+
+        if ($onlyactive) {
+            $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
+            $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
+            $now = round(time(), -2); // rounding helps caching in DB
+            $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
+                                                 $prefix.'active'=>ENROL_USER_ACTIVE,
+                                                 $prefix.'now1'=>$now, $prefix.'now2'=>$now));
+        }
     }
+
+    $joins = implode("\n", $joins);
+    $wheres = "WHERE ".implode(" AND ", $wheres);
+
+    $sql = "SELECT DISTINCT {$prefix}u.id
+              FROM {user} {$prefix}u
+            $joins
+           $wheres";
+
+    return array($sql, $params);
 }
 
 /**
- * Does user have a (temporary or real) guest access to course?
+ * Returns list of users enrolled into course.
  *
- * @param stdClass $context
- * @param stdClass|int $user
- * @return bool
+ * @param context $context
+ * @param string $withcapability
+ * @param int $groupid 0 means ignore groups, any other value limits the result by group id
+ * @param string $userfields requested user record fields
+ * @param string $orderby
+ * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
+ * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
+ * @return array of user records
  */
-function is_guest($context, $user = null) {
-    global $USER;
+function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
+    global $DB;
 
-    // first find the course context
-    $coursecontext = get_course_context($context);
+    list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
+    $sql = "SELECT $userfields
+              FROM {user} u
+              JOIN ($esql) je ON je.id = u.id
+             WHERE u.deleted = 0";
 
-    // make sure there is a real user specified
-    if ($user === null) {
-        $userid = isset($USER->id) ? $USER->id : 0;
+    if ($orderby) {
+        $sql = "$sql ORDER BY $orderby";
     } else {
-        $userid = is_object($user) ? $user->id : $user;
+        $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
     }
 
-    if (isguestuser($userid)) {
-        // can not inspect or be enrolled
-        return true;
-    }
+    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+}
 
-    if (has_capability('moodle/course:view', $coursecontext, $user)) {
-        // viewing users appear out of nowhere, they are neither guests nor participants
-        return false;
-    }
+/**
+ * Counts list of users enrolled into course (as per above function)
+ *
+ * @param context $context
+ * @param string $withcapability
+ * @param int $groupid 0 means ignore groups, any other value limits the result by group id
+ * @return array of user records
+ */
+function count_enrolled_users(context $context, $withcapability = '', $groupid = 0) {
+    global $DB;
 
-    // consider only real active enrolments here
-    if (is_enrolled($coursecontext, $user, '', true)) {
-        return false;
+    list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
+    $sql = "SELECT count(u.id)
+              FROM {user} u
+              JOIN ($esql) je ON je.id = u.id
+             WHERE u.deleted = 0";
+
+    return $DB->count_records_sql($sql, $params);
+}
+
+/**
+ * Loads the capability definitions for the component (from file).
+ *
+ * Loads the capability definitions for the component (from file). If no
+ * capabilities are defined for the component, we simply return an empty array.
+ *
+ * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
+ * @return array array of capabilities
+ */
+function load_capability_def($component) {
+    $defpath = get_component_directory($component).'/db/access.php';
+
+    $capabilities = array();
+    if (file_exists($defpath)) {
+        require($defpath);
+        if (!empty(${$component.'_capabilities'})) {
+            // BC capability array name
+            // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
+            debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files');
+            $capabilities = ${$component.'_capabilities'};
+        }
     }
 
-    return true;
+    return $capabilities;
 }
 
+/**
+ * Gets the capabilities that have been cached in the database for this component.
+ *
+ * @param string $component - examples: 'moodle', 'mod_forum'
+ * @return array array of capabilities
+ */
+function get_cached_capabilities($component = 'moodle') {
+    global $DB;
+    return $DB->get_records('capabilities', array('component'=>$component));
+}
 
 /**
- * Returns true if the user has moodle/course:view capability in the course,
- * this is intended for admins, managers (aka small admins), inspectors, etc.
+ * Returns default capabilities for given role archetype.
  *
- * @param stdClass $context
- * @param int|object $user, if null $USER is used
- * @param string $withcapability extra capability name
- * @return bool
+ * @param string $archetype role archetype
+ * @return array
  */
-function is_viewing($context, $user = null, $withcapability = '') {
-    // first find the course context
-    $coursecontext = get_course_context($context);
+function get_default_capabilities($archetype) {
+    global $DB;
 
-    if (isguestuser($user)) {
-        // can not inspect
-        return false;
+    if (!$archetype) {
+        return array();
     }
 
-    if (!has_capability('moodle/course:view', $coursecontext, $user)) {
-        // admins are allowed to inspect courses
-        return false;
-    }
+    $alldefs = array();
+    $defaults = array();
+    $components = array();
+    $allcaps = $DB->get_records('capabilities');
 
-    if ($withcapability and !has_capability($withcapability, $context, $user)) {
-        // site admins always have the capability, but the enrolment above blocks
-        return false;
+    foreach ($allcaps as $cap) {
+        if (!in_array($cap->component, $components)) {
+            $components[] = $cap->component;
+            $alldefs = array_merge($alldefs, load_capability_def($cap->component));
+        }
+    }
+    foreach($alldefs as $name=>$def) {
+        // Use array 'archetypes if available. Only if not specified, use 'legacy'.
+        if (isset($def['archetypes'])) {
+            if (isset($def['archetypes'][$archetype])) {
+                $defaults[$name] = $def['archetypes'][$archetype];
+            }
+        // 'legacy' is for backward compatibility with 1.9 access.php
+        } else {
+            if (isset($def['legacy'][$archetype])) {
+                $defaults[$name] = $def['legacy'][$archetype];
+            }
+        }
     }
 
-    return true;
+    return $defaults;
 }
 
 /**
- * Returns true if user is enrolled (is participating) in course
- * this is intended for students and teachers.
+ * Reset role capabilities to default according to selected role archetype.
+ * If no archetype selected, removes all capabilities.
  *
- * @param object $context
- * @param int|object $user, if null $USER is used, otherwise user object or id expected
- * @param string $withcapability extra capability name
- * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
- * @return bool
+ * @param int $roleid
+ * @return void
  */
-function is_enrolled($context, $user = null, $withcapability = '', $onlyactive = false) {
-    global $USER, $DB;
+function reset_role_capabilities($roleid) {
+    global $DB;
 
-    // first find the course context
-    $coursecontext = get_course_context($context);
-
-    // make sure there is a real user specified
-    if ($user === null) {
-        $userid = isset($USER->id) ? $USER->id : 0;
-    } else {
-        $userid = is_object($user) ? $user->id : $user;
-    }
-
-    if (empty($userid)) {
-        // not-logged-in!
-        return false;
-    } else if (isguestuser($userid)) {
-        // guest account can not be enrolled anywhere
-        return false;
-    }
+    $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
+    $defaultcaps = get_default_capabilities($role->archetype);
 
-    if ($coursecontext->instanceid == SITEID) {
-        // everybody participates on frontpage
-    } else {
-        if ($onlyactive) {
-            $sql = "SELECT ue.*
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
-                      JOIN {user} u ON u.id = ue.userid
-                     WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
-            $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
-            // this result should be very small, better not do the complex time checks in sql for now ;-)
-            $enrolments = $DB->get_records_sql($sql, $params);
-            $now = time();
-            // make sure the enrol period is ok
-            $result = false;
-            foreach ($enrolments as $e) {
-                if ($e->timestart > $now) {
-                    continue;
-                }
-                if ($e->timeend and $e->timeend < $now) {
-                    continue;
-                }
-                $result = true;
-                break;
-            }
-            if (!$result) {
-                return false;
-            }
+    $systemcontext = context_system::instance();
 
-        } else {
-            // any enrolment is good for us here, even outdated, disabled or inactive
-            $sql = "SELECT 'x'
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
-                      JOIN {user} u ON u.id = ue.userid
-                     WHERE ue.userid = :userid AND u.deleted = 0";
-            $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
-            if (!$DB->record_exists_sql($sql, $params)) {
-                return false;
-            }
-        }
-    }
+    $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
 
-    if ($withcapability and !has_capability($withcapability, $context, $userid)) {
-        return false;
+    foreach($defaultcaps as $cap=>$permission) {
+        assign_capability($cap, $permission, $roleid, $systemcontext->id);
     }
-
-    return true;
 }
 
 /**
- * Returns true if the user is able to access the course.
- *
- * This function is in no way, shape, or form a substitute for require_login.
- * It should only be used in circumstances where it is not possible to call require_login
- * such as the navigation.
- *
- * This function checks many of the methods of access to a course such as the view
- * capability, enrollments, and guest access. It also makes use of the cache
- * generated by require_login for guest access.
+ * Updates the capabilities table with the component capability definitions.
+ * If no parameters are given, the function updates the core moodle
+ * capabilities.
  *
- * The flags within the $USER object that are used here should NEVER be used outside
- * of this function can_access_course and require_login. Doing so WILL break future
- * versions.
+ * Note that the absence of the db/access.php capabilities definition file
+ * will cause any stored capabilities for the component to be removed from
+ * the database.
  *
- * @global moodle_database $DB
- * @param stdClass $context
- * @param stdClass|null $user
- * @param string $withcapability Check for this capability as well.
- * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
- * @param boolean $trustcache If set to false guest access will always be checked
- *                             against the enrolment plugins from the course, rather
- *                             than the cache generated by require_login.
- * @return boolean Returns true if the user is able to access the course
+ * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
+ * @return boolean true if success, exception in case of any problems
  */
-function can_access_course($context, $user = null, $withcapability = '', $onlyactive = false, $trustcache = true) {
-    global $DB, $USER;
+function update_capabilities($component = 'moodle') {
+    global $DB, $OUTPUT;
 
-    $coursecontext = get_course_context($context);
-    $courseid = $coursecontext->instanceid;
+    $storedcaps = array();
 
-    // First check the obvious, is the user viewing or is the user enrolled.
-    if (is_viewing($coursecontext, $user, $withcapability) || is_enrolled($coursecontext, $user, $withcapability, $onlyactive)) {
-        // How easy was that!
-        return true;
+    $filecaps = load_capability_def($component);
+    foreach($filecaps as $capname=>$unused) {
+        if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
+            debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
+        }
     }
 
-    $access = false;
-    if (!isset($USER->enrol)) {
-        // Cache hasn't been generated yet so we can't trust it
-        $trustcache = false;
-        /**
-         * These flags within the $USER object should NEVER be used outside of this
-         * function can_access_course and the function require_login.
-         * Doing so WILL break future versions!!!!
-         */
-        $USER->enrol = array();
-        $USER->enrol['enrolled'] = array();
-        $USER->enrol['tempguest'] = array();
-    }
+    $cachedcaps = get_cached_capabilities($component);
+    if ($cachedcaps) {
+        foreach ($cachedcaps as $cachedcap) {
+            array_push($storedcaps, $cachedcap->name);
+            // update risk bitmasks and context levels in existing capabilities if needed
+            if (array_key_exists($cachedcap->name, $filecaps)) {
+                if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
+                    $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
+                }
+                if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
+                    $updatecap = new stdClass();
+                    $updatecap->id = $cachedcap->id;
+                    $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
+                    $DB->update_record('capabilities', $updatecap);
+                }
+                if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
+                    $updatecap = new stdClass();
+                    $updatecap->id = $cachedcap->id;
+                    $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
+                    $DB->update_record('capabilities', $updatecap);
+                }
 
-    // If we don't trust the cache we need to check with the courses enrolment
-    // plugin instances to see if the user can access the course as a guest.
-    if (!$trustcache) {
-        // Ok, off to the database we go!
-        $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
-        $enrols = enrol_get_plugins(true);
-        foreach($instances as $instance) {
-            if (!isset($enrols[$instance->enrol])) {
-                continue;
+                if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
+                    $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
+                }
+                if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
+                    $updatecap = new stdClass();
+                    $updatecap->id = $cachedcap->id;
+                    $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
+                    $DB->update_record('capabilities', $updatecap);
+                }
             }
-            $until = $enrols[$instance->enrol]->try_guestaccess($instance);
-            if ($until !== false) {
-                // Never use me anywhere but here and require_login
-                $USER->enrol['tempguest'][$courseid] = $until;
-                $access = true;
-                break;
+        }
+    }
+
+    // Are there new capabilities in the file definition?
+    $newcaps = array();
+
+    foreach ($filecaps as $filecap => $def) {
+        if (!$storedcaps ||
+                ($storedcaps && in_array($filecap, $storedcaps) === false)) {
+            if (!array_key_exists('riskbitmask', $def)) {
+                $def['riskbitmask'] = 0; // no risk if not specified
             }
+            $newcaps[$filecap] = $def;
         }
     }
+    // Add new capabilities to the stored definition.
+    foreach ($newcaps as $capname => $capdef) {
+        $capability = new stdClass();
+        $capability->name         = $capname;
+        $capability->captype      = $capdef['captype'];
+        $capability->contextlevel = $capdef['contextlevel'];
+        $capability->component    = $component;
+        $capability->riskbitmask  = $capdef['riskbitmask'];
 
-    // If we don't already have access (from above) check the cache and see whether
-    // there is record of it in there.
-    if (!$access && isset($USER->enrol['tempguest'][$courseid])) {
-        // Never use me anywhere but here and require_login
-        if ($USER->enrol['tempguest'][$courseid] == 0) {
-            $access = true;
-        } else if ($USER->enrol['tempguest'][$courseid] > time()) {
-            $access = true;
-        } else {
-            //expired
-            unset($USER->enrol['tempguest'][$courseid]);
+        $DB->insert_record('capabilities', $capability, false);
+
+        if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
+            if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
+                foreach ($rolecapabilities as $rolecapability){
+                    //assign_capability will update rather than insert if capability exists
+                    if (!assign_capability($capname, $rolecapability->permission,
+                                            $rolecapability->roleid, $rolecapability->contextid, true)){
+                         echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
+                    }
+                }
+            }
+        // we ignore archetype key if we have cloned permissions
+        } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
+            assign_legacy_capabilities($capname, $capdef['archetypes']);
+        // 'legacy' is for backward compatibility with 1.9 access.php
+        } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
+            assign_legacy_capabilities($capname, $capdef['legacy']);
         }
     }
-    return $access;
+    // Are there any capabilities that have been removed from the file
+    // definition that we need to delete from the stored capabilities and
+    // role assignments?
+    capabilities_cleanup($component, $filecaps);
+
+    // reset static caches
+    accesslib_clear_all_caches(false);
+
+    return true;
 }
 
 /**
- * Returns array with sql code and parameters returning all ids
- * of users enrolled into course.
- *
- * This function is using 'eu[0-9]+_' prefix for table names and parameters.
+ * Deletes cached capabilities that are no longer needed by the component.
+ * Also unassigns these capabilities from any roles that have them.
  *
- * @param object $context
- * @param string $withcapability
- * @param int $groupid 0 means ignore groups, any other value limits the result by group id
- * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
- * @return array list($sql, $params)
+ * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
+ * @param array $newcapdef array of the new capability definitions that will be
+ *                     compared with the cached capabilities
+ * @return int number of deprecated capabilities that have been removed
  */
-function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) {
-    global $DB, $CFG;
-
-    // use unique prefix just in case somebody makes some SQL magic with the result
-    static $i = 0;
-    $i++;
-    $prefix = 'eu'.$i.'_';
-
-    // first find the course context
-    $coursecontext = get_course_context($context);
+function capabilities_cleanup($component, $newcapdef = null) {
+    global $DB;
 
-    $isfrontpage = ($coursecontext->instanceid == SITEID);
+    $removedcount = 0;
 
-    $joins  = array();
-    $wheres = array();
-    $params = array();
+    if ($cachedcaps = get_cached_capabilities($component)) {
+        foreach ($cachedcaps as $cachedcap) {
+            if (empty($newcapdef) ||
+                        array_key_exists($cachedcap->name, $newcapdef) === false) {
 
-    list($contextids, $contextpaths) = get_context_info_list($context);
+                // Remove from capabilities cache.
+                $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
+                $removedcount++;
+                // Delete from roles.
+                if ($roles = get_roles_with_capability($cachedcap->name)) {
+                    foreach($roles as $role) {
+                        if (!unassign_capability($cachedcap->name, $role->id)) {
+                            print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
+                        }
+                    }
+                }
+            } // End if.
+        }
+    }
+    return $removedcount;
+}
+
+
+
+//////////////////
+// UI FUNCTIONS //
+//////////////////
+
+/**
+ * Returns an array of all the known types of risk
+ * The array keys can be used, for example as CSS class names, or in calls to
+ * print_risk_icon. The values are the corresponding RISK_ constants.
+ *
+ * @return array all the known types of risk.
+ */
+function get_all_risks() {
+    return array(
+        'riskmanagetrust' => RISK_MANAGETRUST,
+        'riskconfig'      => RISK_CONFIG,
+        'riskxss'         => RISK_XSS,
+        'riskpersonal'    => RISK_PERSONAL,
+        'riskspam'        => RISK_SPAM,
+        'riskdataloss'    => RISK_DATALOSS,
+    );
+}
+
+/**
+ * Return a link to moodle docs for a given capability name
+ *
+ * @param object $capability a capability - a row from the mdl_capabilities table.
+ * @return string the human-readable capability name as a link to Moodle Docs.
+ */
+function get_capability_docs_link($capability) {
+    $url = get_docs_url('Capabilities/' . $capability->name);
+    return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
+}
+
+/**
+ * This function pulls out all the resolved capabilities (overrides and
+ * defaults) of a role used in capability overrides in contexts at a given
+ * context.
+ *
+ * @param context $context
+ * @param int $roleid
+ * @param string $cap capability, optional, defaults to ''
+ * @return array of capabilities
+ */
+function role_context_capabilities($roleid, context $context, $cap = '') {
+    global $DB;
+
+    $contexts = $context->get_parent_context_ids(true);
+    $contexts = '('.implode(',', $contexts).')';
+
+    $params = array($roleid);
+
+    if ($cap) {
+        $search = " AND rc.capability = ? ";
+        $params[] = $cap;
+    } else {
+        $search = '';
+    }
+
+    $sql = "SELECT rc.*
+              FROM {role_capabilities} rc, {context} c
+             WHERE rc.contextid in $contexts
+                   AND rc.roleid = ?
+                   AND rc.contextid = c.id $search
+          ORDER BY c.contextlevel DESC, rc.capability DESC";
+
+    $capabilities = array();
+
+    if ($records = $DB->get_records_sql($sql, $params)) {
+        // We are traversing via reverse order.
+        foreach ($records as $record) {
+            // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
+            if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
+                $capabilities[$record->capability] = $record->permission;
+            }
+        }
+    }
+    return $capabilities;
+}
+
+/**
+ * Constructs array with contextids as first parameter and context paths,
+ * in both cases bottom top including self.
+ *
+ * @private
+ * @param context $context
+ * @return array
+ */
+function get_context_info_list(context $context) {
+    $contextids = explode('/', ltrim($context->path, '/'));
+    $contextpaths = array();
+    $contextids2 = $contextids;
+    while ($contextids2) {
+        $contextpaths[] = '/' . implode('/', $contextids2);
+        array_pop($contextids2);
+    }
+    return array($contextids, $contextpaths);
+}
+
+/**
+ * Check if context is the front page context or a context inside it
+ *
+ * Returns true if this context is the front page context, or a context inside it,
+ * otherwise false.
+ *
+ * @param context $context a context object.
+ * @return bool
+ */
+function is_inside_frontpage(context $context) {
+    $frontpagecontext = context_course::instance(SITEID);
+    return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
+}
+
+/**
+ * Returns capability information (cached)
+ *
+ * @param string $capabilityname
+ * @return object or null if capability not found
+ */
+function get_capability_info($capabilityname) {
+    global $ACCESSLIB_PRIVATE, $DB; // one request per page only
+
+    //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page
+
+    if (empty($ACCESSLIB_PRIVATE->capabilities)) {
+        $ACCESSLIB_PRIVATE->capabilities = array();
+        $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask');
+        foreach ($caps as $cap) {
+            $capname = $cap->name;
+            unset($cap->id);
+            unset($cap->name);
+            $cap->riskbitmask = (int)$cap->riskbitmask;
+            $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap;
+        }
+    }
+
+    return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null;
+}
+
+/**
+ * Returns the human-readable, translated version of the capability.
+ * Basically a big switch statement.
+ *
+ * @param string $capabilityname e.g. mod/choice:readresponses
+ * @return string
+ */
+function get_capability_string($capabilityname) {
+
+    // Typical capability name is 'plugintype/pluginname:capabilityname'
+    list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
+
+    if ($type === 'moodle') {
+        $component = 'core_role';
+    } else if ($type === 'quizreport') {
+        //ugly hack!!
+        $component = 'quiz_'.$name;
+    } else {
+        $component = $type.'_'.$name;
+    }
+
+    $stringname = $name.':'.$capname;
+
+    if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
+        return get_string($stringname, $component);
+    }
+
+    $dir = get_component_directory($component);
+    if (!file_exists($dir)) {
+        // plugin broken or does not exist, do not bother with printing of debug message
+        return $capabilityname.' ???';
+    }
+
+    // something is wrong in plugin, better print debug
+    return get_string($stringname, $component);
+}
+
+/**
+ * This gets the mod/block/course/core etc strings.
+ *
+ * @param string $component
+ * @param int $contextlevel
+ * @return string|bool String is success, false if failed
+ */
+function get_component_string($component, $contextlevel) {
+
+    if ($component === 'moodle' or $component === 'core') {
+        switch ($contextlevel) {
+            // TODO: this should probably use context level names instead
+            case CONTEXT_SYSTEM:    return get_string('coresystem');
+            case CONTEXT_USER:      return get_string('users');
+            case CONTEXT_COURSECAT: return get_string('categories');
+            case CONTEXT_COURSE:    return get_string('course');
+            case CONTEXT_MODULE:    return get_string('activities');
+            case CONTEXT_BLOCK:     return get_string('block');
+            default:                print_error('unknowncontext');
+        }
+    }
+
+    list($type, $name) = normalize_component($component);
+    $dir = get_plugin_directory($type, $name);
+    if (!file_exists($dir)) {
+        // plugin not installed, bad luck, there is no way to find the name
+        return $component.' ???';
+    }
+
+    switch ($type) {
+        // TODO: this is really hacky, anyway it should be probably moved to lib/pluginlib.php
+        case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
+        case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
+        case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
+        case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
+        case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
+        case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
+        case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
+        case 'mod':
+            if (get_string_manager()->string_exists('pluginname', $component)) {
+                return get_string('activity').': '.get_string('pluginname', $component);
+            } else {
+                return get_string('activity').': '.get_string('modulename', $component);
+            }
+        default: return get_string('pluginname', $component);
+    }
+}
+
+/**
+ * Gets the list of roles assigned to this context and up (parents)
+ * from the list of roles that are visible on user profile page
+ * and participants page.
+ *
+ * @param context $context
+ * @return array
+ */
+function get_profile_roles(context $context) {
+    global $CFG, $DB;
+
+    if (empty($CFG->profileroles)) {
+        return array();
+    }
+
+    list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
+    list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
+    $params = array_merge($params, $cparams);
+
+    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
+              FROM {role_assignments} ra, {role} r
+             WHERE r.id = ra.roleid
+                   AND ra.contextid $contextlist
+                   AND r.id $rallowed
+          ORDER BY r.sortorder ASC";
+
+    return $DB->get_records_sql($sql, $params);
+}
+
+/**
+ * Gets the list of roles assigned to this context and up (parents)
+ *
+ * @param context $context
+ * @return array
+ */
+function get_roles_used_in_context(context $context) {
+    global $DB;
+
+    list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true));
+
+    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
+              FROM {role_assignments} ra, {role} r
+             WHERE r.id = ra.roleid
+                   AND ra.contextid $contextlist
+          ORDER BY r.sortorder ASC";
+
+    return $DB->get_records_sql($sql, $params);
+}
+
+/**
+ * This function is used to print roles column in user profile page.
+ * It is using the CFG->profileroles to limit the list to only interesting roles.
+ * (The permission tab has full details of user role assignments.)
+ *
+ * @param int $userid
+ * @param int $courseid
+ * @return string
+ */
+function get_user_roles_in_course($userid, $courseid) {
+    global $CFG, $DB;
+
+    if (empty($CFG->profileroles)) {
+        return '';
+    }
+
+    if ($courseid == SITEID) {
+        $context = context_system::instance();
+    } else {
+        $context = context_course::instance($courseid);
+    }
+
+    if (empty($CFG->profileroles)) {
+        return array();
+    }
+
+    list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
+    list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
+    $params = array_merge($params, $cparams);
+
+    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
+              FROM {role_assignments} ra, {role} r
+             WHERE r.id = ra.roleid
+                   AND ra.contextid $contextlist
+                   AND r.id $rallowed
+                   AND ra.userid = :userid
+          ORDER BY r.sortorder ASC";
+    $params['userid'] = $userid;
+
+    $rolestring = '';
+
+    if ($roles = $DB->get_records_sql($sql, $params)) {
+        foreach ($roles as $userrole) {
+            $rolenames[$userrole->id] = $userrole->name;
+        }
+
+        $rolenames = role_fix_names($rolenames, $context);   // Substitute aliases
+
+        foreach ($rolenames as $roleid => $rolename) {
+            $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
+        }
+        $rolestring = implode(',', $rolenames);
+    }
+
+    return $rolestring;
+}
+
+/**
+ * Checks if a user can assign users to a particular role in this context
+ *
+ * @param context $context
+ * @param int $targetroleid - the id of the role you want to assign users to
+ * @return boolean
+ */
+function user_can_assign(context $context, $targetroleid) {
+    global $DB;
+
+    // first check if user has override capability
+    // if not return false;
+    if (!has_capability('moodle/role:assign', $context)) {
+        return false;
+    }
+    // pull out all active roles of this user from this context(or above)
+    if ($userroles = get_user_roles($context)) {
+        foreach ($userroles as $userrole) {
+            // if any in the role_allow_override table, then it's ok
+            if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Returns all site roles in correct sort order.
+ *
+ * @return array
+ */
+function get_all_roles() {
+    global $DB;
+    return $DB->get_records('role', null, 'sortorder ASC');
+}
+
+/**
+ * Returns roles of a specified archetype
+ *
+ * @param string $archetype
+ * @return array of full role records
+ */
+function get_archetype_roles($archetype) {
+    global $DB;
+    return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
+}
+
+/**
+ * Gets all the user roles assigned in this context, or higher contexts
+ * this is mainly used when checking if a user can assign a role, or overriding a role
+ * i.e. we need to know what this user holds, in order to verify against allow_assign and
+ * allow_override tables
+ *
+ * @param context $context
+ * @param int $userid
+ * @param bool $checkparentcontexts defaults to true
+ * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
+ * @return array
+ */
+function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
+    global $USER, $DB;
+
+    if (empty($userid)) {
+        if (empty($USER->id)) {
+            return array();
+        }
+        $userid = $USER->id;
+    }
+
+    if ($checkparentcontexts) {
+        $contextids = $context->get_parent_context_ids();
+    } else {
+        $contextids = array();
+    }
+    $contextids[] = $context->id;
+
+    list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
+
+    array_unshift($params, $userid);
+
+    $sql = "SELECT ra.*, r.name, r.shortname
+              FROM {role_assignments} ra, {role} r, {context} c
+             WHERE ra.userid = ?
+                   AND ra.roleid = r.id
+                   AND ra.contextid = c.id
+                   AND ra.contextid $contextids
+          ORDER BY $order";
+
+    return $DB->get_records_sql($sql ,$params);
+}
+
+/**
+ * Creates a record in the role_allow_override table
+ *
+ * @param int $sroleid source roleid
+ * @param int $troleid target roleid
+ * @return void
+ */
+function allow_override($sroleid, $troleid) {
+    global $DB;
+
+    $record = new stdClass();
+    $record->roleid        = $sroleid;
+    $record->allowoverride = $troleid;
+    $DB->insert_record('role_allow_override', $record);
+}
+
+/**
+ * Creates a record in the role_allow_assign table
+ *
+ * @param int $fromroleid source roleid
+ * @param int $targetroleid target roleid
+ * @return void
+ */
+function allow_assign($fromroleid, $targetroleid) {
+    global $DB;
+
+    $record = new stdClass();
+    $record->roleid      = $fromroleid;
+    $record->allowassign = $targetroleid;
+    $DB->insert_record('role_allow_assign', $record);
+}
+
+/**
+ * Creates a record in the role_allow_switch table
+ *
+ * @param int $fromroleid source roleid
+ * @param int $targetroleid target roleid
+ * @return void
+ */
+function allow_switch($fromroleid, $targetroleid) {
+    global $DB;
+
+    $record = new stdClass();
+    $record->roleid      = $fromroleid;
+    $record->allowswitch = $targetroleid;
+    $DB->insert_record('role_allow_switch', $record);
+}
+
+/**
+ * Gets a list of roles that this user can assign in this context
+ *
+ * @param context $context the context.
+ * @param int $rolenamedisplay the type of role name to display. One of the
+ *      ROLENAME_X constants. Default ROLENAME_ALIAS.
+ * @param bool $withusercounts if true, count the number of users with each role.
+ * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
+ * @return array if $withusercounts is false, then an array $roleid => $rolename.
+ *      if $withusercounts is true, returns a list of three arrays,
+ *      $rolenames, $rolecounts, and $nameswithcounts.
+ */
+function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
+    global $USER, $DB;
+
+    // make sure there is a real user specified
+    if ($user === null) {
+        $userid = isset($USER->id) ? $USER->id : 0;
+    } else {
+        $userid = is_object($user) ? $user->id : $user;
+    }
+
+    if (!has_capability('moodle/role:assign', $context, $userid)) {
+        if ($withusercounts) {
+            return array(array(), array(), array());
+        } else {
+            return array();
+        }
+    }
+
+    $parents = $context->get_parent_context_ids(true);
+    $contexts = implode(',' , $parents);
+
+    $params = array();
+    $extrafields = '';
+    if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT or $rolenamedisplay == ROLENAME_SHORT) {
+        $extrafields .= ', r.shortname';
+    }
+
+    if ($withusercounts) {
+        $extrafields = ', (SELECT count(u.id)
+                             FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
+                            WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
+                          ) AS usercount';
+        $params['conid'] = $context->id;
+    }
+
+    if (is_siteadmin($userid)) {
+        // show all roles allowed in this context to admins
+        $assignrestriction = "";
+    } else {
+        $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
+                                      FROM {role_allow_assign} raa
+                                      JOIN {role_assignments} ra ON ra.roleid = raa.roleid
+                                     WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
+                                   ) ar ON ar.id = r.id";
+        $params['userid'] = $userid;
+    }
+    $params['contextlevel'] = $context->contextlevel;
+    $sql = "SELECT r.id, r.name $extrafields
+              FROM {role} r
+              $assignrestriction
+              JOIN {role_context_levels} rcl ON r.id = rcl.roleid
+             WHERE rcl.contextlevel = :contextlevel
+          ORDER BY r.sortorder ASC";
+    $roles = $DB->get_records_sql($sql, $params);
+
+    $rolenames = array();
+    foreach ($roles as $role) {
+        if ($rolenamedisplay == ROLENAME_SHORT) {
+            $rolenames[$role->id] = $role->shortname;
+            continue;
+        }
+        $rolenames[$role->id] = $role->name;
+        if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
+            $rolenames[$role->id] .= ' (' . $role->shortname . ')';
+        }
+    }
+    if ($rolenamedisplay != ROLENAME_ORIGINALANDSHORT and $rolenamedisplay != ROLENAME_SHORT) {
+        $rolenames = role_fix_names($rolenames, $context, $rolenamedisplay);
+    }
+
+    if (!$withusercounts) {
+        return $rolenames;
+    }
+
+    $rolecounts = array();
+    $nameswithcounts = array();
+    foreach ($roles as $role) {
+        $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
+        $rolecounts[$role->id] = $roles[$role->id]->usercount;
+    }
+    return array($rolenames, $rolecounts, $nameswithcounts);
+}
+
+/**
+ * Gets a list of roles that this user can switch to in a context
+ *
+ * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
+ * This function just process the contents of the role_allow_switch table. You also need to
+ * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
+ *
+ * @param context $context a context.
+ * @return array an array $roleid => $rolename.
+ */
+function get_switchable_roles(context $context) {
+    global $USER, $DB;
+
+    $params = array();
+    $extrajoins = '';
+    $extrawhere = '';
+    if (!is_siteadmin()) {
+        // Admins are allowed to switch to any role with.
+        // Others are subject to the additional constraint that the switch-to role must be allowed by
+        // 'role_allow_switch' for some role they have assigned in this context or any parent.
+        $parents = $context->get_parent_context_ids(true);
+        $contexts = implode(',' , $parents);
+
+        $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
+        JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
+        $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
+        $params['userid'] = $USER->id;
+    }
+
+    $query = "
+        SELECT r.id, r.name
+          FROM (SELECT DISTINCT rc.roleid
+                  FROM {role_capabilities} rc
+                  $extrajoins
+                  $extrawhere) idlist
+          JOIN {role} r ON r.id = idlist.roleid
+      ORDER BY r.sortorder";
+
+    $rolenames = $DB->get_records_sql_menu($query, $params);
+    return role_fix_names($rolenames, $context, ROLENAME_ALIAS);
+}
+
+/**
+ * Gets a list of roles that this user can override in this context.
+ *
+ * @param context $context the context.
+ * @param int $rolenamedisplay the type of role name to display. One of the
+ *      ROLENAME_X constants. Default ROLENAME_ALIAS.
+ * @param bool $withcounts if true, count the number of overrides that are set for each role.
+ * @return array if $withcounts is false, then an array $roleid => $rolename.
+ *      if $withusercounts is true, returns a list of three arrays,
+ *      $rolenames, $rolecounts, and $nameswithcounts.
+ */
+function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
+    global $USER, $DB;
+
+    if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
+        if ($withcounts) {
+            return array(array(), array(), array());
+        } else {
+            return array();
+        }
+    }
+
+    $parents = $context->get_parent_context_ids(true);
+    $contexts = implode(',' , $parents);
+
+    $params = array();
+    $extrafields = '';
+    if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
+        $extrafields .= ', ro.shortname';
+    }
+
+    $params['userid'] = $USER->id;
+    if ($withcounts) {
+        $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
+                WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
+        $params['conid'] = $context->id;
+    }
+
+    if (is_siteadmin()) {
+        // show all roles to admins
+        $roles = $DB->get_records_sql("
+            SELECT ro.id, ro.name$extrafields
+              FROM {role} ro
+          ORDER BY ro.sortorder ASC", $params);
+
+    } else {
+        $roles = $DB->get_records_sql("
+            SELECT ro.id, ro.name$extrafields
+              FROM {role} ro
+              JOIN (SELECT DISTINCT r.id
+                      FROM {role} r
+                      JOIN {role_allow_override} rao ON r.id = rao.allowoverride
+                      JOIN {role_assignments} ra ON rao.roleid = ra.roleid
+                     WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
+                   ) inline_view ON ro.id = inline_view.id
+          ORDER BY ro.sortorder ASC", $params);
+    }
+
+    $rolenames = array();
+    foreach ($roles as $role) {
+        $rolenames[$role->id] = $role->name;
+        if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
+            $rolenames[$role->id] .= ' (' . $role->shortname . ')';
+        }
+    }
+    if ($rolenamedisplay != ROLENAME_ORIGINALANDSHORT) {
+        $rolenames = role_fix_names($rolenames, $context, $rolenamedisplay);
+    }
+
+    if (!$withcounts) {
+        return $rolenames;
+}
+
+    $rolecounts = array();
+    $nameswithcounts = array();
+    foreach ($roles as $role) {
+        $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
+        $rolecounts[$role->id] = $roles[$role->id]->overridecount;
+    }
+    return array($rolenames, $rolecounts, $nameswithcounts);
+}
+
+/**
+ * Create a role menu suitable for default role selection in enrol plugins.
+ * @param context $context
+ * @param int $addroleid current or default role - always added to list
+ * @return array roleid=>localised role name
+ */
+function get_default_enrol_roles(context $context, $addroleid = null) {
+    global $DB;
+
+    $params = array('contextlevel'=>CONTEXT_COURSE);
+    if ($addroleid) {
+        $addrole = "OR r.id = :addroleid";
+        $params['addroleid'] = $addroleid;
+    } else {
+        $addrole = "";
+    }
+    $sql = "SELECT r.id, r.name
+              FROM {role} r
+         LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
+             WHERE rcl.id IS NOT NULL $addrole
+          ORDER BY sortorder DESC";
+
+    $roles = $DB->get_records_sql_menu($sql, $params);
+    $roles = role_fix_names($roles, $context, ROLENAME_BOTH);
+
+    return $roles;
+}
+
+/**
+ * Return context levels where this role is assignable.
+ * @param integer $roleid the id of a role.
+ * @return array list of the context levels at which this role may be assigned.
+ */
+function get_role_contextlevels($roleid) {
+    global $DB;
+    return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
+            'contextlevel', 'id,contextlevel');
+}
+
+/**
+ * Return roles suitable for assignment at the specified context level.
+ *
+ * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
+ *
+ * @param integer $contextlevel a contextlevel.
+ * @return array list of role ids that are assignable at this context level.
+ */
+function get_roles_for_contextlevels($contextlevel) {
+    global $DB;
+    return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
+            '', 'id,roleid');
+}
+
+/**
+ * Returns default context levels where roles can be assigned.
+ *
+ * @param string $rolearchetype one of the role archetypes - that is, one of the keys
+ *      from the array returned by get_role_archetypes();
+ * @return array list of the context levels at which this type of role may be assigned by default.
+ */
+function get_default_contextlevels($rolearchetype) {
+    static $defaults = array(
+        'manager'        => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
+        'coursecreator'  => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
+        'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
+        'teacher'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
+        'student'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
+        'guest'          => array(),
+        'user'           => array(),
+        'frontpage'      => array());
+
+    if (isset($defaults[$rolearchetype])) {
+        return $defaults[$rolearchetype];
+    } else {
+        return array();
+    }
+}
+
+/**
+ * Set the context levels at which a particular role can be assigned.
+ * Throws exceptions in case of error.
+ *
+ * @param integer $roleid the id of a role.
+ * @param array $contextlevels the context levels at which this role should be assignable,
+ *      duplicate levels are removed.
+ * @return void
+ */
+function set_role_contextlevels($roleid, array $contextlevels) {
+    global $DB;
+    $DB->delete_records('role_context_levels', array('roleid' => $roleid));
+    $rcl = new stdClass();
+    $rcl->roleid = $roleid;
+    $contextlevels = array_unique($contextlevels);
+    foreach ($contextlevels as $level) {
+        $rcl->contextlevel = $level;
+        $DB->insert_record('role_context_levels', $rcl, false, true);
+    }
+}
 
-    // get all relevant capability info for all roles
-    if ($withcapability) {
-        list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx');
-        $cparams['cap'] = $withcapability;
+/**
+ * Who has this capability in this context?
+ *
+ * This can be a very expensive call - use sparingly and keep
+ * the results if you are going to need them again soon.
+ *
+ * Note if $fields is empty this function attempts to get u.*
+ * which can get rather large - and has a serious perf impact
+ * on some DBs.
+ *
+ * @param context $context
+ * @param string|array $capability - capability name(s)
+ * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
+ * @param string $sort - the sort order. Default is lastaccess time.
+ * @param mixed $limitfrom - number of records to skip (offset)
+ * @param mixed $limitnum - number of records to fetch
+ * @param string|array $groups - single group or array of groups - only return
+ *               users who are in one of these group(s).
+ * @param string|array $exceptions - list of users to exclude, comma separated or array
+ * @param bool $doanything_ignored not used any more, admin accounts are never returned
+ * @param bool $view_ignored - use get_enrolled_sql() instead
+ * @param bool $useviewallgroups if $groups is set the return users who
+ *               have capability both $capability and moodle/site:accessallgroups
+ *               in this context, as well as users who have $capability and who are
+ *               in $groups.
+ * @return mixed
+ */
+function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
+                                 $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
+    global $CFG, $DB;
 
-        $defs = array();
-        $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
-                  FROM {role_capabilities} rc
-                  JOIN {context} ctx on rc.contextid = ctx.id
-                 WHERE rc.contextid $incontexts AND rc.capability = :cap";
-        $rcs = $DB->get_records_sql($sql, $cparams);
-        foreach ($rcs as $rc) {
-            $defs[$rc->path][$rc->roleid] = $rc->permission;
+    $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
+    $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
+
+    $ctxids = trim($context->path, '/');
+    $ctxids = str_replace('/', ',', $ctxids);
+
+    // Context is the frontpage
+    $iscoursepage = false; // coursepage other than fp
+    $isfrontpage = false;
+    if ($context->contextlevel == CONTEXT_COURSE) {
+        if ($context->instanceid == SITEID) {
+            $isfrontpage = true;
+        } else {
+            $iscoursepage = true;
         }
+    }
+    $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
 
-        $access = array();
-        if (!empty($defs)) {
-            foreach ($contextpaths as $path) {
-                if (empty($defs[$path])) {
+    $caps = (array)$capability;
+
+    // construct list of context paths bottom-->top
+    list($contextids, $paths) = get_context_info_list($context);
+
+    // we need to find out all roles that have these capabilities either in definition or in overrides
+    $defs = array();
+    list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
+    list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
+    $params = array_merge($params, $params2);
+    $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
+              FROM {role_capabilities} rc
+              JOIN {context} ctx on rc.contextid = ctx.id
+             WHERE rc.contextid $incontexts AND rc.capability $incaps";
+
+    $rcs = $DB->get_records_sql($sql, $params);
+    foreach ($rcs as $rc) {
+        $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
+    }
+
+    // go through the permissions bottom-->top direction to evaluate the current permission,
+    // first one wins (prohibit is an exception that always wins)
+    $access = array();
+    foreach ($caps as $cap) {
+        foreach ($paths as $path) {
+            if (empty($defs[$cap][$path])) {
+                continue;
+            }
+            foreach($defs[$cap][$path] as $roleid => $perm) {
+                if ($perm == CAP_PROHIBIT) {
+                    $access[$cap][$roleid] = CAP_PROHIBIT;
                     continue;
                 }
-                foreach($defs[$path] as $roleid => $perm) {
-                    if ($perm == CAP_PROHIBIT) {
-                        $access[$roleid] = CAP_PROHIBIT;
-                        continue;
-                    }
-                    if (!isset($access[$roleid])) {
-                        $access[$roleid] = (int)$perm;
-                    }
+                if (!isset($access[$cap][$roleid])) {
+                    $access[$cap][$roleid] = (int)$perm;
                 }
             }
         }
+    }
 
-        unset($defs);
-
-        // make lists of roles that are needed and prohibited
-        $needed     = array(); // one of these is enough
-        $prohibited = array(); // must not have any of these
-        foreach ($access as $roleid => $perm) {
+    // make lists of roles that are needed and prohibited in this context
+    $needed = array(); // one of these is enough
+    $prohibited = array(); // must not have any of these
+    foreach ($caps as $cap) {
+        if (empty($access[$cap])) {
+            continue;
+        }
+        foreach ($access[$cap] as $roleid => $perm) {
             if ($perm == CAP_PROHIBIT) {
-                unset($needed[$roleid]);
-                $prohibited[$roleid] = true;
-            } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
-                $needed[$roleid] = true;
+                unset($needed[$cap][$roleid]);
+                $prohibited[$cap][$roleid] = true;
+            } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
+                $needed[$cap][$roleid] = true;
             }
         }
+        if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
+            // easy, nobody has the permission
+            unset($needed[$cap]);
+            unset($prohibited[$cap]);
+        } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
+            // everybody is disqualified on the frontapge
+            unset($needed[$cap]);
+            unset($prohibited[$cap]);
+        }
+        if (empty($prohibited[$cap])) {
+            unset($prohibited[$cap]);
+        }
+    }
 
-        $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
-        $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
+    if (empty($needed)) {
+        // there can not be anybody if no roles match this request
+        return array();
+    }
+
+    if (empty($prohibited)) {
+        // we can compact the needed roles
+        $n = array();
+        foreach ($needed as $cap) {
+            foreach ($cap as $roleid=>$unused) {
+                $n[$roleid] = true;
+            }
+        }
+        $needed = array('any'=>$n);
+        unset($n);
+    }
+
+    /// ***** Set up default fields ******
+    if (empty($fields)) {
+        if ($iscoursepage) {
+            $fields = 'u.*, ul.timeaccess AS lastaccess';
+        } else {
+            $fields = 'u.*';
+        }
+    } else {
+        if (debugging('', DEBUG_DEVELOPER) && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
+            debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
+        }
+    }
+
+    /// Set up default sort
+    if (empty($sort)) { // default to course lastaccess or just lastaccess
+        if ($iscoursepage) {
+            $sort = 'ul.timeaccess';
+        } else {
+            $sort = 'u.lastaccess';
+        }
+    }
+
+    // Prepare query clauses
+    $wherecond = array();
+    $params    = array();
+    $joins     = array();
+
+    // User lastaccess JOIN
+    if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
+         // user_lastaccess is not required MDL-13810
+    } else {
+        if ($iscoursepage) {
+            $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
+        } else {
+            throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
+        }
+    }
+
+    /// We never return deleted users or guest account.
+    $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
+    $params['guestid'] = $CFG->siteguest;
+
+    /// Groups
+    if ($groups) {
+        $groups = (array)$groups;
+        list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
+        $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)";
+        $params = array_merge($params, $grpparams);
+
+        if ($useviewallgroups) {
+            $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
+            if (!empty($viewallgroupsusers)) {
+                $wherecond[] =  "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))';
+            } else {
+                $wherecond[] =  "($grouptest)";
+            }
+        } else {
+            $wherecond[] =  "($grouptest)";
+        }
+    }
+
+    /// User exceptions
+    if (!empty($exceptions)) {
+        $exceptions = (array)$exceptions;
+        list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
+        $params = array_merge($params, $exparams);
+        $wherecond[] = "u.id $exsql";
+    }
+
+    // now add the needed and prohibited roles conditions as joins
+    if (!empty($needed['any'])) {
+        // simple case - there are no prohibits involved
+        if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
+            // everybody
+        } else {
+            $joins[] = "JOIN (SELECT DISTINCT userid
+                                FROM {role_assignments}
+                               WHERE contextid IN ($ctxids)
+                                     AND roleid IN (".implode(',', array_keys($needed['any'])) .")
+                             ) ra ON ra.userid = u.id";
+        }
+    } else {
+        $unions = array();
+        $everybody = false;
+        foreach ($needed as $cap=>$unused) {
+            if (empty($prohibited[$cap])) {
+                if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
+                    $everybody = true;
+                    break;
+                } else {
+                    $unions[] = "SELECT userid
+                                   FROM {role_assignments}
+                                  WHERE contextid IN ($ctxids)
+                                        AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
+                }
+            } else {
+                if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
+                    // nobody can have this cap because it is prevented in default roles
+                    continue;
 
-        $nobody = false;
+                } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
+                    // everybody except the prohibitted - hiding does not matter
+                    $unions[] = "SELECT id AS userid
+                                   FROM {user}
+                                  WHERE id NOT IN (SELECT userid
+                                                     FROM {role_assignments}
+                                                    WHERE contextid IN ($ctxids)
+                                                          AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
 
-        if ($isfrontpage) {
-            if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
-                $nobody = true;
-            } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
-                // everybody not having prohibit has the capability
-                $needed = array();
-            } else if (empty($needed)) {
-                $nobody = true;
+                } else {
+                    $unions[] = "SELECT userid
+                                   FROM {role_assignments}
+                                  WHERE contextid IN ($ctxids)
+                                        AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
+                                        AND roleid NOT IN (".implode(',', array_keys($prohibited[$cap])) .")";
+                }
             }
-        } else {
-            if (!empty($prohibited[$defaultuserroleid])) {
-                $nobody = true;
-            } else if (!empty($needed[$defaultuserroleid])) {
-                // everybody not having prohibit has the capability
-                $needed = array();
-            } else if (empty($needed)) {
-                $nobody = true;
+        }
+        if (!$everybody) {
+            if ($unions) {
+                $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id";
+            } else {
+                // only prohibits found - nobody can be matched
+                $wherecond[] = "1 = 2";
             }
         }
+    }
 
-        if ($nobody) {
-            // nobody can match so return some SQL that does not return any results
-            $wheres[] = "1 = 2";
-
-        } else {
+    // Collect WHERE conditions and needed joins
+    $where = implode(' AND ', $wherecond);
+    if ($where !== '') {
+        $where = 'WHERE ' . $where;
+    }
+    $joins = implode("\n", $joins);
 
-            if ($needed) {
-                $ctxids = implode(',', $contextids);
-                $roleids = implode(',', array_keys($needed));
-                $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))";
-            }
+    /// Ok, let's get the users!
+    $sql = "SELECT $fields
+              FROM {user} u
+            $joins
+            $where
+          ORDER BY $sort";
 
-            if ($prohibited) {
-                $ctxids = implode(',', $contextids);
-                $roleids = implode(',', array_keys($prohibited));
-                $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))";
-                $wheres[] = "{$prefix}ra4.id IS NULL";
-            }
+    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+}
 
-            if ($groupid) {
-                $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
-                $params["{$prefix}gmid"] = $groupid;
-            }
-        }
+/**
+ * Re-sort a users array based on a sorting policy
+ *
+ * Will re-sort a $users results array (from get_users_by_capability(), usually)
+ * based on a sorting policy. This is to support the odd practice of
+ * sorting teachers by 'authority', where authority was "lowest id of the role
+ * assignment".
+ *
+ * Will execute 1 database query. Only suitable for small numbers of users, as it
+ * uses an u.id IN() clause.
+ *
+ * Notes about the sorting criteria.
+ *
+ * As a default, we cannot rely on role.sortorder because then
+ * admins/coursecreators will always win. That is why the sane
+ * rule "is locality matters most", with sortorder as 2nd
+ * consideration.
+ *
+ * If you want role.sortorder, use the 'sortorder' policy, and
+ * name explicitly what roles you want to cover. It's probably
+ * a good idea to see what roles have the capabilities you want
+ * (array_diff() them against roiles that have 'can-do-anything'
+ * to weed out admin-ish roles. Or fetch a list of roles from
+ * variables like $CFG->coursecontact .
+ *
+ * @param array $users Users array, keyed on userid
+ * @param context $context
+ * @param array $roles ids of the roles to include, optional
+ * @param string $sortpolicy defaults to locality, more about
+ * @return array sorted copy of the array
+ */
+function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
+    global $DB;
 
+    $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
+    $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
+    if (empty($roles)) {
+        $roleswhere = '';
     } else {
-        if ($groupid) {
-            $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
-            $params["{$prefix}gmid"] = $groupid;
-        }
+        $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
     }
 
-    $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
-    $params["{$prefix}guestid"] = $CFG->siteguest;
-
-    if ($isfrontpage) {
-        // all users are "enrolled" on the frontpage
-    } else {
-        $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
-        $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
-        $params[$prefix.'courseid'] = $coursecontext->instanceid;
+    $sql = "SELECT ra.userid
+              FROM {role_assignments} ra
+              JOIN {role} r
+                   ON ra.roleid=r.id
+              JOIN {context} ctx
+                   ON ra.contextid=ctx.id
+             WHERE $userswhere
+                   $contextwhere
+                   $roleswhere";
 
-        if ($onlyactive) {
-            $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
-            $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
-            $now = round(time(), -2); // rounding helps caching in DB
-            $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
-                                                 $prefix.'active'=>ENROL_USER_ACTIVE,
-                                                 $prefix.'now1'=>$now, $prefix.'now2'=>$now));
-        }
+    // Default 'locality' policy -- read PHPDoc notes
+    // about sort policies...
+    $orderby = 'ORDER BY '
+                    .'ctx.depth DESC, '  /* locality wins */
+                    .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
+                    .'ra.id';            /* role assignment order tie-breaker */
+    if ($sortpolicy === 'sortorder') {
+        $orderby = 'ORDER BY '
+                        .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
+                        .'ra.id';            /* role assignment order tie-breaker */
     }
 
-    $joins = implode("\n", $joins);
-    $wheres = "WHERE ".implode(" AND ", $wheres);
+    $sortedids = $DB->get_fieldset_sql($sql . $orderby);
+    $sortedusers = array();
+    $seen = array();
 
-    $sql = "SELECT DISTINCT {$prefix}u.id
-               FROM {user} {$prefix}u
-             $joins
-            $wheres";
+    foreach ($sortedids as $id) {
+        // Avoid duplicates
+        if (isset($seen[$id])) {
+            continue;
+        }
+        $seen[$id] = true;
 
-    return array($sql, $params);
+        // assign
+        $sortedusers[$id] = $users[$id];
+    }
+    return $sortedusers;
 }
 
 /**
- * Returns list of users enrolled into course.
- * @param object $context
- * @param string $withcapability
- * @param int $groupid 0 means ignore groups, any other value limits the result by group id
- * @param string $userfields requested user record fields
- * @param string $orderby
- * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
- * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
- * @return array of user records
+ * Gets all the users assigned this role in this context or higher
+ *
+ * @param int $roleid (can also be an array of ints!)
+ * @param context $context
+ * @param bool $parent if true, get list of users assigned in higher context too
+ * @param string $fields fields from user (u.) , role assignment (ra) or role (r.)
+ * @param string $sort sort from user (u.) , role assignment (ra) or role (r.)
+ * @param bool $gethidden_ignored use enrolments instead
+ * @param string $group defaults to ''
+ * @param mixed $limitfrom defaults to ''
+ * @param mixed $limitnum defaults to ''
+ * @param string $extrawheretest defaults to ''
+ * @param string|array $whereparams defaults to ''
+ * @return array
  */
-function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
+function get_role_users($roleid, context $context, $parent = false, $fields = '',
+        $sort = 'u.lastname, u.firstname', $gethidden_ignored = null, $group = '',
+        $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereparams = array()) {
     global $DB;
 
-    list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
-    $sql = "SELECT $userfields
-              FROM {user} u
-              JOIN ($esql) je ON je.id = u.id
-             WHERE u.deleted = 0";
+    if (empty($fields)) {
+        $fields = 'u.id, u.confirmed, u.username, u.firstname, u.lastname, '.
+                  'u.maildisplay, u.mailformat, u.maildigest, u.email, u.city, '.
+                  'u.country, u.picture, u.idnumber, u.department, u.institution, '.
+                  'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder';
+    }
 
-    if ($orderby) {
-        $sql = "$sql ORDER BY $orderby";
+    $parentcontexts = '';
+    if ($parent) {
+        $parentcontexts = substr($context->path, 1); // kill leading slash
+        $parentcontexts = str_replace('/', ',', $parentcontexts);
+        if ($parentcontexts !== '') {
+            $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
+        }
+    }
+
+    if ($roleid) {
+        list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
+        $roleselect = "AND ra.roleid $rids";
     } else {
-        $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
+        $params = array();
+        $roleselect = '';
     }
 
-    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
-}
+    if ($group) {
+        $groupjoin   = "JOIN {groups_members} gm ON gm.userid = u.id";
+        $groupselect = " AND gm.groupid = ? ";
+        $params[] = $group;
+    } else {
+        $groupjoin   = '';
+        $groupselect = '';
+    }
 
-/**
- * Counts list of users enrolled into course (as per above function)
- * @param object $context
- * @param string $withcapability
- * @param int $groupid 0 means ignore groups, any other value limits the result by group id
- * @return array of user records
- */
-function count_enrolled_users($context, $withcapability = '', $groupid = 0) {
-    global $DB;
+    array_unshift($params, $context->id);
 
-    list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
-    $sql = "SELECT count(u.id)
-              FROM {user} u
-              JOIN ($esql) je ON je.id = u.id
-             WHERE u.deleted = 0";
+    if ($extrawheretest) {
+        $extrawheretest = ' AND ' . $extrawheretest;
+        $params = array_merge($params, $whereparams);
+    }
+
+    $sql = "SELECT DISTINCT $fields, ra.roleid
+              FROM {role_assignments} ra
+              JOIN {user} u ON u.id = ra.userid
+              JOIN {role} r ON ra.roleid = r.id
+        $groupjoin
+             WHERE (ra.contextid = ? $parentcontexts)
+                   $roleselect
+                   $groupselect
+                   $extrawheretest
+          ORDER BY $sort";                  // join now so that we can just use fullname() later
 
-    return $DB->count_records_sql($sql, $params);
+    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
 }
 
-
 /**
- * Loads the capability definitions for the component (from file).
- *
- * Loads the capability definitions for the component (from file). If no
- * capabilities are defined for the component, we simply return an empty array.
+ * Counts all the users assigned this role in this context or higher
  *
- * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
- * @return array array of capabilities
+ * @param int|array $roleid either int or an array of ints
+ * @param context $context
+ * @param bool $parent if true, get list of users assigned in higher context too
+ * @return int Returns the result count
  */
-function load_capability_def($component) {
-    $defpath = get_component_directory($component).'/db/access.php';
+function count_role_users($roleid, context $context, $parent = false) {
+    global $DB;
 
-    $capabilities = array();
-    if (file_exists($defpath)) {
-        require($defpath);
-        if (!empty(${$component.'_capabilities'})) {
-            // BC capability array name
-            // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
-            debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files');
-            $capabilities = ${$component.'_capabilities'};
+    if ($parent) {
+        if ($contexts = $context->get_parent_context_ids()) {
+            $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
+        } else {
+            $parentcontexts = '';
         }
+    } else {
+        $parentcontexts = '';
     }
 
-    return $capabilities;
-}
+    if ($roleid) {
+        list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
+        $roleselect = "AND r.roleid $rids";
+    } else {
+        $params = array();
+        $roleselect = '';
+    }
 
+    array_unshift($params, $context->id);
 
-/**
- * Gets the capabilities that have been cached in the database for this component.
- * @param string $component - examples: 'moodle', 'mod_forum'
- * @return array array of capabilities
- */
-function get_cached_capabilities($component = 'moodle') {
-    global $DB;
-    return $DB->get_records('capabilities', array('component'=>$component));
+    $sql = "SELECT COUNT(u.id)
+              FROM {role_assignments} r
+              JOIN {user} u ON u.id = r.userid
+             WHERE (r.contextid = ? $parentcontexts)
+                   $roleselect
+                   AND u.deleted = 0";
+
+    return $DB->count_records_sql($sql, $params);
 }
 
 /**
- * Returns default capabilities for given role archetype.
- * @param string $archetype role archetype
- * @return array
+ * This function gets the list of courses that this user has a particular capability in.
+ * It is still not very efficient.
+ *
+ * @param string $capability Capability in question
+ * @param int $userid User ID or null for current user
+ * @param bool $doanything True if 'doanything' is permitted (default)
+ * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
+ *   otherwise use a comma-separated list of the fields you require, not including id
+ * @param string $orderby If set, use a comma-separated list of fields from course
+ *   table with sql modifiers (DESC) if needed
+ * @return array Array of courses, may have zero entries. Or false if query failed.
  */
-function get_default_capabilities($archetype) {
+function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '') {
     global $DB;
 
-    if (!$archetype) {
-        return array();
-    }
-
-    $alldefs = array();
-    $defaults = array();
-    $components = array();
-    $allcaps = $DB->get_records('capabilities');
-
-    foreach ($allcaps as $cap) {
-        if (!in_array($cap->component, $components)) {
-            $components[] = $cap->component;
-            $alldefs = array_merge($alldefs, load_capability_def($cap->component));
+    // Convert fields list and ordering
+    $fieldlist = '';
+    if ($fieldsexceptid) {
+        $fields = explode(',', $fieldsexceptid);
+        foreach($fields as $field) {
+            $fieldlist .= ',c.'.$field;
         }
     }
-    foreach($alldefs as $name=>$def) {
-        // Use array 'archetypes if available. Only if not specified, use 'legacy'.
-        if (isset($def['archetypes'])) {
-            if (isset($def['archetypes'][$archetype])) {
-                $defaults[$name] = $def['archetypes'][$archetype];
-            }
-        // 'legacy' is for backward compatibility with 1.9 access.php
-        } else {
-            if (isset($def['legacy'][$archetype])) {
-                $defaults[$name] = $def['legacy'][$archetype];
+    if ($orderby) {
+        $fields = explode(',', $orderby);
+        $orderby = '';
+        foreach($fields as $field) {
+            if ($orderby) {
+                $orderby .= ',';
             }
+            $orderby .= 'c.'.$field;
         }
+        $orderby = 'ORDER BY '.$orderby;
     }
 
-    return $defaults;
+    // Obtain a list of everything relevant about all courses including context.
+    // Note the result can be used directly as a context (we are going to), the course
+    // fields are just appended.
+
+    $courses = array();
+    $rs = $DB->get_recordset_sql("SELECT x.*, c.id AS courseid $fieldlist
+                                    FROM {course} c
+                                   INNER JOIN {context} x
+                                         ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.")
+                                $orderby");
+    // Check capability for each course in turn
+    foreach ($rs as $coursecontext) {
+        if (has_capability($capability, $coursecontext, $userid, $doanything)) {
+            // We've got the capability. Make the record look like a course record
+            // and store it
+            $coursecontext->id = $coursecontext->courseid;
+            unset($coursecontext->courseid);
+            unset($coursecontext->contextlevel);
+            unset($coursecontext->instanceid);
+            $courses[] = $coursecontext;
+        }
+    }
+    $rs->close();
+    return empty($courses) ? false : $courses;
 }
 
 /**
- * Reset role capabilities to default according to selected role archetype.
- * If no archetype selected, removes all capabilities.
- * @param int $roleid
- * @return void
+ * This function finds the roles assigned directly to this context only
+ * i.e. no roles in parent contexts
+ *
+ * @param context $context
+ * @return array
  */
-function reset_role_capabilities($roleid) {
+function get_roles_on_exact_context(context $context) {
     global $DB;
 
-    $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
-    $defaultcaps = get_default_capabilities($role->archetype);
-
-    $sitecontext = get_context_instance(CONTEXT_SYSTEM);
-
-    $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
+    return $DB->get_records_sql("SELECT r.*
+                                   FROM {role_assignments} ra, {role} r
+                                  WHERE ra.roleid = r.id AND ra.contextid = ?",
+                                array($context->id));
 
-    foreach($defaultcaps as $cap=>$permission) {
-        assign_capability($cap, $permission, $roleid, $sitecontext->id);
-    }
 }
 
 /**
- * Updates the capabilities table with the component capability definitions.
- * If no parameters are given, the function updates the core moodle
- * capabilities.
+ * Switches the current user to another role for the current session and only
+ * in the given context.
  *
- * Note that the absence of the db/access.php capabilities definition file
- * will cause any stored capabilities for the component to be removed from
- * the database.
+ * The caller *must* check
+ * - that this op is allowed
+ * - that the requested role can be switched to in this context (use get_switchable_roles)
+ * - that the requested role is NOT $CFG->defaultuserroleid
  *
- * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
- * @return boolean true if success, exception in case of any problems
+ * To "unswitch" pass 0 as the roleid.
+ *
+ * This function *will* modify $USER->access - beware
+ *
+ * @param integer $roleid the role to switch to.
+ * @param context $context the context in which to perform the switch.
+ * @return bool success or failure.
  */
-function update_capabilities($component = 'moodle') {
-    global $DB, $OUTPUT, $ACCESSLIB_PRIVATE;
-
-    $storedcaps = array();
-
-    $filecaps = load_capability_def($component);
-    foreach($filecaps as $capname=>$unused) {
-        if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
-            debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
-        }
-    }
-
-    $cachedcaps = get_cached_capabilities($component);
-    if ($cachedcaps) {
-        foreach ($cachedcaps as $cachedcap) {
-            array_push($storedcaps, $cachedcap->name);
-            // update risk bitmasks and context levels in existing capabilities if needed
-            if (array_key_exists($cachedcap->name, $filecaps)) {
-                if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
-                    $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
-                }
-                if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
-                    $updatecap = new stdClass();
-                    $updatecap->id = $cachedcap->id;
-                    $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
-                    $DB->update_record('capabilities', $updatecap);
-                }
-                if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
-                    $updatecap = new stdClass();
-                    $updatecap->id = $cachedcap->id;
-                    $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
-                    $DB->update_record('capabilities', $updatecap);
-                }
-
-                if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
-                    $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
-                }
-                if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
-                    $updatecap = new stdClass();
-                    $updatecap->id = $cachedcap->id;
-                    $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
-                    $DB->update_record('capabilities', $updatecap);
-                }
-            }
-        }
-    }
-
-    // Are there new capabilities in the file definition?
-    $newcaps = array();
+function role_switch($roleid, context $context) {
+    global $USER;
 
-    foreach ($filecaps as $filecap => $def) {
-        if (!$storedcaps ||
-                ($storedcaps && in_array($filecap, $storedcaps) === false)) {
-            if (!array_key_exists('riskbitmask', $def)) {
-                $def['riskbitmask'] = 0; // no risk if not specified
-            }
-            $newcaps[$filecap] = $def;
-        }
-    }
-    // Add new capabilities to the stored definition.
-    foreach ($newcaps as $capname => $capdef) {
-        $capability = new stdClass();
-        $capability->name         = $capname;
-        $capability->captype      = $capdef['captype'];
-        $capability->contextlevel = $capdef['contextlevel'];
-        $capability->component    = $component;
-        $capability->riskbitmask  = $capdef['riskbitmask'];
+    //
+    // Plan of action
+    //
+    // - Add the ghost RA to $USER->access
+    //   as $USER->access['rsw'][$path] = $roleid
+    //
+    // - Make sure $USER->access['rdef'] has the roledefs
+    //   it needs to honour the switcherole
+    //
+    // Roledefs will get loaded "deep" here - down to the last child
+    // context. Note that
+    //
+    // - When visiting subcontexts, our selective accessdata loading
+    //   will still work fine - though those ra/rdefs will be ignored
+    //   appropriately while the switch is in place
+    //
+    // - If a switcherole happens at a category with tons of courses
+    //   (that have many overrides for switched-to role), the session
+    //   will get... quite large. Sometimes you just can't win.
+    //
+    // To un-switch just unset($USER->access['rsw'][$path])
+    //
+    // Note: it is not possible to switch to roles that do not have course:view
 
-        $DB->insert_record('capabilities', $capability, false);
+    // Add the switch RA
+    if (!isset($USER->access['rsw'])) {
+        $USER->access['rsw'] = array();
+    }
 
-        if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
-            if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
-                foreach ($rolecapabilities as $rolecapability){
-                    //assign_capability will update rather than insert if capability exists
-                    if (!assign_capability($capname, $rolecapability->permission,
-                                            $rolecapability->roleid, $rolecapability->contextid, true)){
-                         echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
-                    }
-                }
-            }
-        // we ignore archetype key if we have cloned permissions
-        } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
-            assign_legacy_capabilities($capname, $capdef['archetypes']);
-        // 'legacy' is for backward compatibility with 1.9 access.php
-        } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
-            assign_legacy_capabilities($capname, $capdef['legacy']);
+    if ($roleid == 0) {
+        unset($USER->access['rsw'][$context->path]);
+        if (empty($USER->access['rsw'])) {
+            unset($USER->access['rsw']);
         }
+        return true;
     }
-    // Are there any capabilities that have been removed from the file
-    // definition that we need to delete from the stored capabilities and
-    // role assignments?
-    capabilities_cleanup($component, $filecaps);
 
-    // reset static caches
-    $ACCESSLIB_PRIVATE->capabilities = null;
+    $USER->access['rsw'][$context->path] = $roleid;
+
+    // Load roledefs
+    load_role_access_by_context($roleid, $context, $USER->access);
 
     return true;
 }
 
+/**
+ * Checks if the user has switched roles within the given course.
+ *
+ * Note: You can only switch roles within the course, hence it takes a courseid
+ * rather than a context. On that note Petr volunteered to implement this across
+ * all other contexts, all requests for this should be forwarded to him ;)
+ *
+ * @param int $courseid The id of the course to check
+ * @return bool True if the user has switched roles within the course.
+ */
+function is_role_switched($courseid) {
+    global $USER;
+    $context = context_course::instance($courseid, MUST_EXIST);
+    return (!empty($USER->access['rsw'][$context->path]));
+}
 
 /**
- * Deletes cached capabilities that are no longer needed by the component.
- * Also unassigns these capabilities from any roles that have them.
+ * Get any role that has an override on exact context
  *
- * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
- * @param array $newcapdef array of the new capability definitions that will be
- *                     compared with the cached capabilities
- * @return int number of deprecated capabilities that have been removed
+ * @param context $context The context to check
+ * @return array An array of roles
  */
-function capabilities_cleanup($component, $newcapdef = null) {
+function get_roles_with_override_on_context(context $context) {
     global $DB;
 
-    $removedcount = 0;
+    return $DB->get_records_sql("SELECT r.*
+                                   FROM {role_capabilities} rc, {role} r
+                                  WHERE rc.roleid = r.id AND rc.contextid = ?",
+                                array($context->id));
+}
 
-    if ($cachedcaps = get_cached_capabilities($component)) {
-        foreach ($cachedcaps as $cachedcap) {
-            if (empty($newcapdef) ||
-                        array_key_exists($cachedcap->name, $newcapdef) === false) {
+/**
+ * Get all capabilities for this role on this context (overrides)
+ *
+ * @param stdClass $role
+ * @param context $context
+ * @return array
+ */
+function get_capabilities_from_role_on_context($role, context $context) {
+    global $DB;
 
-                // Remove from capabilities cache.
-                $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
-                $removedcount++;
-                // Delete from roles.
-                if ($roles = get_roles_with_capability($cachedcap->name)) {
-                    foreach($roles as $role) {
-                        if (!unassign_capability($cachedcap->name, $role->id)) {
-                            print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
-                        }
-                    }
-                }
-            } // End if.
-        }
-    }
-    return $removedcount;
+    return $DB->get_records_sql("SELECT *
+                                   FROM {role_capabilities}
+                                  WHERE contextid = ? AND roleid = ?",
+                                array($context->id, $role->id));
 }
 
+/**
+ * Find out which roles has assignment on this context
+ *
+ * @param context $context
+ * @return array
+ *
+ */
+function get_roles_with_assignment_on_context(context $context) {
+    global $DB;
+
+    return $DB->get_records_sql("SELECT r.*
+                                   FROM {role_assignments} ra, {role} r
+                                  WHERE ra.roleid = r.id AND ra.contextid = ?",
+                                array($context->id));
+}
 
+/**
+ * Find all user assignment of users for this role, on this context
+ *
+ * @param stdClass $role
+ * @param context $context
+ * @return array
+ */
+function get_users_from_role_on_context($role, context $context) {
+    global $DB;
 
-//////////////////
-// UI FUNCTIONS //
-//////////////////
+    return $DB->get_records_sql("SELECT *
+                                   FROM {role_assignments}
+                                  WHERE contextid = ? AND roleid = ?",
+                                array($context->id, $role->id));
+}
 
 /**
- * @param integer $contextlevel $context->context level. One of the CONTEXT_... constants.
- * @return string the name for this type of context.
+ * Simple function returning a boolean true if user has roles
+ * in context or parent contexts, otherwise false.
+ *
+ * @param int $userid
+ * @param int $roleid
+ * @param int $contextid empty means any context
+ * @return bool
  */
-function get_contextlevel_name($contextlevel) {
-    static $strcontextlevels = null;
-    if (is_null($strcontextlevels)) {
-        $strcontextlevels = array(
-            CONTEXT_SYSTEM    => get_string('coresystem'),
-            CONTEXT_USER      => get_string('user'),
-            CONTEXT_COURSECAT => get_string('category'),
-            CONTEXT_COURSE    => get_string('course'),
-            CONTEXT_MODULE    => get_string('activitymodule'),
-            CONTEXT_BLOCK     => get_string('block')
-        );
+function user_has_role_assignment($userid, $roleid, $contextid = 0) {
+    global $DB;
+
+    if ($contextid) {
+        if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) {
+            return false;
+        }
+        $parents = $context->get_parent_context_ids(true);
+        list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r');
+        $params['userid'] = $userid;
+        $params['roleid'] = $roleid;
+
+        $sql = "SELECT COUNT(ra.id)
+                  FROM {role_assignments} ra
+                 WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts";
+
+        $count = $DB->get_field_sql($sql, $params);
+        return ($count > 0);
+
+    } else {
+        return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid));
     }
-    return $strcontextlevels[$contextlevel];
 }
 
 /**
- * Prints human readable context identifier.
+ * Get role name or alias if exists and format the text.
  *
- * @param object $context the context.
- * @param boolean $withprefix whether to prefix the name of the context with the
- *      type of context, e.g. User, Course, Forum, etc.
- * @param boolean $short whether to user the short name of the thing. Only applies
- *      to course contexts
- * @return string the human readable context name.
+ * @param stdClass $role role object
+ * @param context_course $coursecontext
+ * @return string name of role in course context
  */
-function print_context_name($context, $withprefix = true, $short = false) {
+function role_get_name($role, context_course $coursecontext) {
     global $DB;
 
-    $name = '';
-    switch ($context->contextlevel) {
-
-        case CONTEXT_SYSTEM:
-            $name = get_string('coresystem');
-            break;
+    if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) {
+        return strip_tags(format_string($r->name));
+    } else {
+        return strip_tags(format_string($role->name));
+    }
+}
 
-        case CONTEXT_USER:
-            if ($user = $DB->get_record('user', array('id'=>$context->instanceid))) {
-                if ($withprefix){
-                    $name = get_string('user').': ';
-                }
-                $name .= fullname($user);
-            }
-            break;
+/**
+ * Prepare list of roles for display, apply aliases and format text
+ *
+ * @param array $roleoptions array roleid => rolename or roleid => roleobject
+ * @param context $context a context
+ * @param int $rolenamedisplay
+ * @return array Array of context-specific role names, or role objexts with a ->localname field added.
+ */
+function role_fix_names($roleoptions, context $context, $rolenamedisplay = ROLENAME_ALIAS) {
+    global $DB;
 
-        case CONTEXT_COURSECAT:
-            if ($category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) {
-                if ($withprefix){
-                    $name = get_string('category').': ';
-                }
-                $name .= format_string($category->name, true, array('context' => $context));
-            }
-            break;
+    // Make sure we have a course context.
+    $coursecontext = $context->get_course_context(false);
 
-        case CONTEXT_COURSE:
-            if ($context->instanceid == SITEID) {
-                $name = get_string('frontpage', 'admin');
+    // Make sure we are working with an array roleid => name. Normally we
+    // want to use the unlocalised name if the localised one is not present.
+    $newnames = array();
+    foreach ($roleoptions as $rid => $roleorname) {
+        if ($rolenamedisplay != ROLENAME_ALIAS_RAW) {
+            if (is_object($roleorname)) {
+                $newnames[$rid] = $roleorname->name;
             } else {
-                if ($course = $DB->get_record('course', array('id'=>$context->instanceid))) {
-                    if ($withprefix){
-                        $name = get_string('course').': ';
-                    }
-                    if ($short){
-                        $name .= format_string($course->shortname, true, array('context' => $context));
-                    } else {
-                        $name .= format_string($course->fullname);
-                   }
-                }
+                $newnames[$rid] = $roleorname;
             }
-            break;
-
-        case CONTEXT_MODULE:
-            if ($cm = $DB->get_record_sql('SELECT cm.*, md.name AS modname FROM {course_modules} cm ' .
-                    'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
-                if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
-                        if ($withprefix){
-                        $name = get_string('modulename', $cm->modname).': ';
-                        }
-                        $name .= $mod->name;
-                    }
-                }
-            break;
+        } else {
+            $newnames[$rid] = '';
+        }
+    }
 
-        case CONTEXT_BLOCK:
-            if ($blockinstance = $DB->get_record('block_instances', array('id'=>$context->instanceid))) {
-                global $CFG;
-                require_once("$CFG->dirroot/blocks/moodleblock.class.php");
-                require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
-                $blockname = "block_$blockinstance->blockname";
-                if ($blockobject = new $blockname()) {
-                    if ($withprefix){
-                        $name = get_string('block').': ';
-                    }
-                    $name .= $blockobject->title;
+    // If necessary, get the localised names.
+    if ($rolenamedisplay != ROLENAME_ORIGINAL && !empty($coursecontext->id)) {
+        // The get the relevant renames, and use them.
+        $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
+        foreach ($aliasnames as $alias) {
+            if (isset($newnames[$alias->roleid])) {
+                if ($rolenamedisplay == ROLENAME_ALIAS || $rolenamedisplay == ROLENAME_ALIAS_RAW) {
+                    $newnames[$alias->roleid] = $alias->name;
+                } else if ($rolenamedisplay == ROLENAME_BOTH) {
+                    $newnames[$alias->roleid] = $alias->name . ' (' . $roleoptions[$alias->roleid] . ')';
                 }
             }
-            break;
-
-        default:
-            print_error('unknowncontext');
-            return false;
+        }
     }
 
-    return $name;
+    // Finally, apply format_string and put the result in the right place.
+    foreach ($roleoptions as $rid => $roleorname) {
+        if ($rolenamedisplay != ROLENAME_ALIAS_RAW) {
+            $newnames[$rid] = strip_tags(format_string($newnames[$rid]));
+        }
+        if (is_object($roleorname)) {
+            $roleoptions[$rid]->localname = $newnames[$rid];
+        } else {
+            $roleoptions[$rid] = $newnames[$rid];
+        }
+    }
+    return $roleoptions;
 }
 
 /**
- * Get a URL for a context, if there is a natural one. For example, for
- * CONTEXT_COURSE, this is the course page. For CONTEXT_USER it is the
- * user profile page.
+ * Aids in detecting if a new line is required when reading a new capability
  *
- * @param object $context the context.
- * @return moodle_url
- */
-function get_context_url($context) {
-    global $COURSE, $DB;
-
-    switch ($context->contextlevel) {
-        case CONTEXT_USER:
-            if ($COURSE->id == SITEID) {
-                $url = new moodle_url('/user/profile.php', array('id'=>$context->instanceid));
-            } else {
-                $url = new moodle_url('/user/view.php', array('id'=>$context->instanceid, 'courseid'=>$COURSE->id));
-            }
-            return $url;;
+ * This function helps admin/roles/manage.php etc to detect if a new line should be printed
+ * when we read in a new capability.
+ * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
+ * but when we are in grade, all reports/import/export capabilities should be together
+ *
+ * @param string $cap component string a
+ * @param string $comp component string b
+ * @param int $contextlevel
+ * @return bool whether 2 component are in different "sections"
+ */
+function component_level_changed($cap, $comp, $contextlevel) {
 
-        case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
-            return new moodle_url('/course/category.php', array('id'=>$context->instanceid));
+    if (strstr($cap->component, '/') && strstr($comp, '/')) {
+        $compsa = explode('/', $cap->component);
+        $compsb = explode('/', $comp);
 
-        case CONTEXT_COURSE: // 1 to 1 to course cat
-            if ($context->instanceid != SITEID) {
-                return new moodle_url('/course/view.php', array('id'=>$context->instanceid));
-            }
-            break;
+        // list of system reports
+        if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
+            return false;
+        }
 
-        case CONTEXT_MODULE: // 1 to 1 to course
-            if ($modname = $DB->get_field_sql('SELECT md.name AS modname FROM {course_modules} cm ' .
-                    'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
-                return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$context->instanceid));
-            }
-            break;
+        // we are in gradebook, still
+        if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') &&
+            ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) {
+            return false;
+        }
 
-        case CONTEXT_BLOCK:
-            $parentcontexts = get_parent_contexts($context, false);
-            $parent = reset($parentcontexts);
-            $parent = get_context_instance_by_id($parent);
-            return get_context_url($parent);
+        if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
+            return false;
+        }
     }
 
-    return new moodle_url('/');
+    return ($cap->component != $comp || $cap->contextlevel != $contextlevel);
 }
 
 /**
- * Returns an array of all the known types of risk
- * The array keys can be used, for example as CSS class names, or in calls to
- * print_risk_icon. The values are the corresponding RISK_ constants.
+ * Fix the roles.sortorder field in the database, so it contains sequential integers,
+ * and return an array of roleids in order.
  *
- * @return array all the known types of risk.
+ * @param array $allroles array of roles, as returned by get_all_roles();
+ * @return array $role->sortorder =-> $role->id with the keys in ascending order.
  */
-function get_all_risks() {
-    return array(
-        'riskmanagetrust' => RISK_MANAGETRUST,
-        'riskconfig'      => RISK_CONFIG,
-        'riskxss'         => RISK_XSS,
-        'riskpersonal'    => RISK_PERSONAL,
-        'riskspam'        => RISK_SPAM,
-        'riskdataloss'    => RISK_DATALOSS,
-    );
+function fix_role_sortorder($allroles) {
+    global $DB;
+
+    $rolesort = array();
+    $i = 0;
+    foreach ($allroles as $role) {
+        $rolesort[$i] = $role->id;
+        if ($role->sortorder != $i) {
+            $r = new stdClass();
+            $r->id = $role->id;
+            $r->sortorder = $i;
+            $DB->update_record('role', $r);
+            $allroles[$role->id]->sortorder = $i;
+        }
+        $i++;
+    }
+    return $rolesort;
 }
 
 /**
- * Return a link to moodle docs for a given capability name
+ * Switch the sort order of two roles (used in admin/roles/manage.php).
  *
- * @param object $capability a capability - a row from the mdl_capabilities table.
- * @return string the human-readable capability name as a link to Moodle Docs.
+ * @param object $first The first role. Actually, only ->sortorder is used.
+ * @param object $second The second role. Actually, only ->sortorder is used.
+ * @return boolean success or failure
  */
-function get_capability_docs_link($capability) {
-    global $CFG;
-    $url = get_docs_url('Capabilities/' . $capability->name);
-    return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
+function switch_roles($first, $second) {
+    global $DB;
+    $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array());
+    $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder));
+    $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder));
+    $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp));
+    return $result;
 }
 
 /**
- * Extracts the relevant capabilities given a contextid.
- * All case based, example an instance of forum context.
- * Will fetch all forum related capabilities, while course contexts
- * Will fetch all capabilities
- *
- * capabilities
- * `name` varchar(150) NOT NULL,
- * `captype` varchar(50) NOT NULL,
- * `contextlevel` int(10) NOT NULL,
- * `component` varchar(100) NOT NULL,
+ * Duplicates all the base definitions of a role
  *
- * @param object context
- * @return array
+ * @param object $sourcerole role to copy from
+ * @param int $targetrole id of role to copy to
  */
-function fetch_context_capabilities($context) {
-    global $DB, $CFG;
-
-    $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
-
-    $params = array();
-
-    switch ($context->contextlevel) {
-
-        case CONTEXT_SYSTEM: // all
-            $SQL = "SELECT *
-                      FROM {capabilities}";
-        break;
-
-        case CONTEXT_USER:
-            $extracaps = array('moodle/grade:viewall');
-            list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
-            $SQL = "SELECT *
-                      FROM {capabilities}
-                     WHERE contextlevel = ".CONTEXT_USER."
-                           OR name $extra";
-        break;
-
-        case CONTEXT_COURSECAT: // course category context and bellow
-            $SQL = "SELECT *
-                      FROM {capabilities}
-                     WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
-        break;
-
-        case CONTEXT_COURSE: // course context and bellow
-            $SQL = "SELECT *
-                      FROM {capabilities}
-                     WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
-        break;
-
-        case CONTEXT_MODULE: // mod caps
-            $cm = $DB->get_record('course_modules', array('id'=>$context->instanceid));
-            $module = $DB->get_record('modules', array('id'=>$cm->module));
-
-            $subcaps = array();
-            $subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php";
-            if (file_exists($subpluginsfile)) {
-                $subplugins = array();  // should be redefined in the file
-                include($subpluginsfile);
-                if (!empty($subplugins)) {
-                    foreach (array_keys($subplugins) as $subplugintype) {
-                        foreach (array_keys(get_plugin_list($subplugintype)) as $subpluginname) {
-                            $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
-                        }
-                    }
-                }
-            }
-
-            $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
-            if (file_exists($modfile)) {
-                include_once($modfile);
-                $modfunction = $module->name.'_get_extra_capabilities';
-                if (function_exists($modfunction)) {
-                    $extracaps = $modfunction();
-                }
-            }
-            if (empty($extracaps)) {
-                $extracaps = array();
-            }
-
-            $extracaps = array_merge($subcaps, $extracaps);
-
-            // All modules allow viewhiddenactivities. This is so you can hide
-            // the module then override to allow specific roles to see it.
-            // The actual check is in course page so not module-specific
-            $extracaps[]="moodle/course:viewhiddenactivities";
-            list($extra, $params) = $DB->get_in_or_equal(
-                $extracaps, SQL_PARAMS_NAMED, 'cap0');
-            $extra = "OR name $extra";
-
-            $SQL = "SELECT *
-                      FROM {capabilities}
-                     WHERE (contextlevel = ".CONTEXT_MODULE."
-                           AND component = :component)
-                           $extra";
-            $params['component'] = "mod_$module->name";
-        break;
-
-        case CONTEXT_BLOCK: // block caps
-            $bi = $DB->get_record('block_instances', array('id' => $context->instanceid));
-
-            $extra = '';
-            $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
-            if ($extracaps) {
-                list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
-                $extra = "OR name $extra";
-            }
-
-            $SQL = "SELECT *
-                      FROM {capabilities}
-                     WHERE (contextlevel = ".CONTEXT_BLOCK."
-                           AND component = :component)
-                           $extra";
-            $params['component'] = 'block_' . $bi->blockname;
-        break;
-
-        default:
-        return false;
-    }
+function role_cap_duplicate($sourcerole, $targetrole) {
+    global $DB;
 
-    if (!$records = $DB->get_records_sql($SQL.' '.$sort, $params)) {
-        $records = array();
+    $systemcontext = context_system::instance();
+    $caps = $DB->get_records_sql("SELECT *
+                                    FROM {role_capabilities}
+                                   WHERE roleid = ? AND contextid = ?",
+                                 array($sourcerole->id, $systemcontext->id));
+    // adding capabilities
+    foreach ($caps as $cap) {
+        unset($cap->id);
+        $cap->roleid = $targetrole;
+        $DB->insert_record('role_capabilities', $cap);
     }
-
-    return $records;
 }
 
-
 /**
- * This function pulls out all the resolved capabilities (overrides and
- * defaults) of a role used in capability overrides in contexts at a given
- * context.
+ * Returns two lists, this can be used to find out if user has capability.
+ * Having any needed role and no forbidden role in this context means
+ * user has this capability in this context.
+ * Use get_role_names_with_cap_in_context() if you need role names to display in the UI
  *
- * @param obj $context
- * @param int $roleid
- * @param string $cap capability, optional, defaults to ''
- * @return array of capabilities
+ * @param object $context
+ * @param string $capability
+ * @return array($neededroles, $forbiddenroles)
  */
-function role_context_capabilities($roleid, $context, $cap = '') {
+function get_roles_with_cap_in_context($context, $capability) {
     global $DB;
 
-    $contexts = get_parent_contexts($context);
-    $contexts[] = $context->id;
-    $contexts = '('.implode(',', $contexts).')';
+    $ctxids = trim($context->path, '/'); // kill leading slash
+    $ctxids = str_replace('/', ',', $ctxids);
 
-    $params = array($roleid);
+    $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
+              FROM {role_capabilities} rc
+              JOIN {context} ctx ON ctx.id = rc.contextid
+             WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
+          ORDER BY rc.roleid ASC, ctx.depth DESC";
+    $params = array('cap'=>$capability);
 
-    if ($cap) {
-        $search = " AND rc.capability = ? ";
-        $params[] = $cap;
-    } else {
-        $search = '';
+    if (!$capdefs = $DB->get_records_sql($sql, $params)) {
+        // no cap definitions --> no capability
+        return array(array(), array());
     }
 
-    $sql = "SELECT rc.*
-              FROM {role_capabilities} rc, {context} c
-             WHERE rc.contextid in $contexts
-                   AND rc.roleid = ?
-                   AND rc.contextid = c.id $search
-          ORDER BY c.contextlevel DESC, rc.capability DESC";
-
-    $capabilities = array();
-
-    if ($records = $DB->get_records_sql($sql, $params)) {
-        // We are traversing via reverse order.
-        foreach ($records as $record) {
-            // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
-            if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
-                $capabilities[$record->capability] = $record->permission;
+    $forbidden = array();
+    $needed    = array();
+    foreach($capdefs as $def) {
+        if (isset($forbidden[$def->roleid])) {
+            continue;
+        }
+        if ($def->permission == CAP_PROHIBIT) {
+            $forbidden[$def->roleid] = $def->roleid;
+            unset($needed[$def->roleid]);
+            continue;
+        }
+        if (!isset($needed[$def->roleid])) {
+            if ($def->permission == CAP_ALLOW) {
+                $needed[$def->roleid] = true;
+            } else if ($def->permission == CAP_PREVENT) {
+                $needed[$def->roleid] = false;
             }
         }
     }
-    return $capabilities;
-}
-
-/**
- * Recursive function which, given a context, find all parent context ids,
- * and return the array in reverse order, i.e. parent first, then grand
- * parent, etc.
- *
- * @param object $context
- * @param bool $capability optional, defaults to false
- * @return array
- */
-function get_parent_contexts($context, $includeself = false) {
-
-    if ($context->path == '') {
-        return array();
-    }
+    unset($capdefs);
 
-    $parentcontexts = substr($context->path, 1); // kill leading slash
-    $parentcontexts = explode('/', $parentcontexts);
-    if (!$includeself) {
-        array_pop($parentcontexts); // and remove its own id
+    // remove all those roles not allowing
+    foreach($needed as $key=>$value) {
+        if (!$value) {
+            unset($needed[$key]);
+        } else {
+            $needed[$key] = $key;
+        }
     }
 
-    return array_reverse($parentcontexts);
+    return array($needed, $forbidden);
 }
 
 /**
- * Return the id of the parent of this context, or false if there is no parent (only happens if this
- * is the site context.)
+ * Returns an array of role IDs that have ALL of the the supplied capabilities
+ * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden
  *
  * @param object $context
- * @return integer the id of the parent context.
+ * @param array $capabilities An array of capabilities
+ * @return array of roles with all of the required capabilities
  */
-function get_parent_contextid($context) {
-    $parentcontexts = get_parent_contexts($context);
-    if (count($parentcontexts) == 0) {
-        return false;
+function get_roles_with_caps_in_context($context, $capabilities) {
+    $neededarr = array();
+    $forbiddenarr = array();
+    foreach($capabilities as $caprequired) {
+        list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired);
     }
-    return array_shift($parentcontexts);
-}
 
-/**
- * Constructs array with contextids as first parameter and context paths,
- * in both cases bottom top including self.
- *
- * @param object $context
- * @return array
- */
-function get_context_info_list($context) {
-    $contextids = explode('/', ltrim($context->path, '/'));
-    $contextpaths = array();
-    $contextids2 = $contextids;
-    while ($contextids2) {
-        $contextpaths[] = '/' . implode('/', $contextids2);
-        array_pop($contextids2);
+    $rolesthatcanrate = array();
+    if (!empty($neededarr)) {
+        foreach ($neededarr as $needed) {
+            if (empty($rolesthatcanrate)) {
+                $rolesthatcanrate = $needed;
+            } else {
+                //only want roles that have all caps
+                $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed);
+            }
+        }
     }
-    return array($contextids, $contextpaths);
+    if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) {
+        foreach ($forbiddenarr as $forbidden) {
+           //remove any roles that are forbidden any of the caps
+           $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden);
+        }
+    }
+    return $rolesthatcanrate;
 }
 
 /**
- * Find course context
- * @param object $context - course or lower context
- * @return object context of the enclosing course, throws exception when related course context can not be found
+ * Returns an array of role names that have ALL of the the supplied capabilities
+ * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden
+ *
+ * @param object $context
+ * @param array $capabilities An array of capabilities
+ * @return array of roles with all of the required capabilities
  */
-function get_course_context($context) {
-    if (empty($context->contextlevel)) {
-        throw new coding_exception('Invalid context parameter.');
-
-    } if ($context->contextlevel == CONTEXT_COURSE) {
-        return $context;
+function get_role_names_with_caps_in_context($context, $capabilities) {
+    global $DB;
 
-    } else if ($context->contextlevel == CONTEXT_MODULE) {
-        return get_context_instance_by_id(get_parent_contextid($context, MUST_EXIST));
+    $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities);
 
-    } else if ($context->contextlevel == CONTEXT_BLOCK) {
-        $parentcontext = get_context_instance_by_id(get_parent_contextid($context, MUST_EXIST));
-        if ($parentcontext->contextlevel == CONTEXT_COURSE) {
-            return $parentcontext;
-        } else if ($parentcontext->contextlevel == CONTEXT_MODULE) {
-            return get_context_instance_by_id(get_parent_contextid($parentcontext, MUST_EXIST));
-        } else {
-            throw new coding_exception('Invalid level of block context parameter.');
-        }
+    $allroles = array();
+    $roles = $DB->get_records('role', null, 'sortorder DESC');
+    foreach ($roles as $roleid=>$role) {
+        $allroles[$roleid] = $role->name;
     }
 
-    throw new coding_exception('Invalid context level of parameter.');
+    $rolenames = array();
+    foreach ($rolesthatcanrate as $r) {
+        $rolenames[$r] = $allroles[$r];
+    }
+    $rolenames = role_fix_names($rolenames, $context);
+    return $rolenames;
 }
 
 /**
- * Check if context is the front page context or a context inside it
- *
- * Returns true if this context is the front page context, or a context inside it,
- * otherwise false.
+ * This function verifies the prohibit comes from this context
+ * and there are no more prohibits in parent contexts.
  *
- * @param object $context a context object.
+ * @param int $roleid
+ * @param context $context
+ * @param string $capability name
  * @return bool
  */
-function is_inside_frontpage($context) {
-    $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
-    return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
+function prohibit_is_removable($roleid, context $context, $capability) {
+    global $DB;
+
+    $ctxids = trim($context->path, '/'); // kill leading slash
+    $ctxids = str_replace('/', ',', $ctxids);
+
+    $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT);
+
+    $sql = "SELECT ctx.id
+              FROM {role_capabilities} rc
+              JOIN {context} ctx ON ctx.id = rc.contextid
+             WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
+          ORDER BY ctx.depth DESC";
+
+    if (!$prohibits = $DB->get_records_sql($sql, $params)) {
+        // no prohibits == nothing to remove
+        return true;
+    }
+
+    if (count($prohibits) > 1) {
+        // more prohibints can not be removed
+        return false;
+    }
+
+    return !empty($prohibits[$context->id]);
 }
 
 /**
- * Runs get_records select on context table and returns the result
- * Does get_records_select on the context table, and returns the results ordered
- * by contextlevel, and then the natural sort order within each level.
- * for the purpose of $select, you need to know that the context table has been
- * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3');
- *
- * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname.
- * @param array $params any parameters required by $select.
- * @return array the requested context records.
+ * More user friendly role permission changing,
+ * it should produce as few overrides as possible.
+ * @param int $roleid
+ * @param object $context
+ * @param string $capname capability name
+ * @param int $permission
+ * @return void
  */
-function get_sorted_contexts($select, $params = array()) {
+function role_change_permission($roleid, $context, $capname, $permission) {
     global $DB;
-    if ($select) {
-        $select = 'WHERE ' . $select;
+
+    if ($permission == CAP_INHERIT) {
+        unassign_capability($capname, $roleid, $context->id);
+        $context->mark_dirty();
+        return;
     }
-    return $DB->get_records_sql("
-            SELECT ctx.*
-              FROM {context} ctx
-              LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid
-              LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid
-              LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid
-              LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid
-              LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid
-           $select
-          ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id
-            ", $params);
-}
 
-/**
- * Recursive function which, given a context, find all its children context ids.
- *
- * When called for a course context, it will return the modules and blocks
- * displayed in the course page.
- *
- * For course category contexts it will return categories and courses. It will
- * NOT recurse into courses, nor return blocks on the category pages. If you
- * want to do that, call it on the returned courses.
- *
- * If called on a course context it _will_ populate the cache with the appropriate
- * contexts ;-)
- *
- * @param object $context.
- * @return array Array of child records
- */
-function get_child_contexts($context) {
+    $ctxids = trim($context->path, '/'); // kill leading slash
+    $ctxids = str_replace('/', ',', $ctxids);
 
-    global $DB, $ACCESSLIB_PRIVATE;
+    $params = array('roleid'=>$roleid, 'cap'=>$capname);
 
-    // We *MUST* populate the context_cache as the callers
-    // will probably ask for the full record anyway soon after
-    // soon after calling us ;-)
-
-    $array = array();
-    $cache = $ACCESSLIB_PRIVATE->contexcache;
-
-    switch ($context->contextlevel) {
-
-        case CONTEXT_BLOCK:
-            // No children.
-        break;
-
-        case CONTEXT_MODULE:
-            // Find
-            // - blocks under this context path.
-            $sql = " SELECT ctx.*
-                       FROM {context} ctx
-                      WHERE ctx.path LIKE ?
-                            AND ctx.contextlevel = ".CONTEXT_BLOCK;
-            $params = array("{$context->path}/%", $context->instanceid);
-            $records = $DB->get_recordset_sql($sql, $params);
-            foreach ($records as $rec) {
-                $cache->add($rec);
-                $array[$rec->id] = $rec;
-            }
-            break;
+    $sql = "SELECT ctx.id, rc.permission, ctx.depth
+              FROM {role_capabilities} rc
+              JOIN {context} ctx ON ctx.id = rc.contextid
+             WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
+          ORDER BY ctx.depth DESC";
 
-        case CONTEXT_COURSE:
-            // Find
-            // - modules and blocks under this context path.
-            $sql = " SELECT ctx.*
-                       FROM {context} ctx
-                      WHERE ctx.path LIKE ?
-                            AND ctx.contextlevel IN (".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
-            $params = array("{$context->path}/%", $context->instanceid);
-            $records = $DB->get_recordset_sql($sql, $params);
-            foreach ($records as $rec) {
-                $cache->add($rec);
-                $array[$rec->id] = $rec;
-            }
-        break;
-
-        case CONTEXT_COURSECAT:
-            // Find
-            // - categories
-            // - courses
-            $sql = " SELECT ctx.*
-                       FROM {context} ctx
-                      WHERE ctx.path LIKE ?
-                            AND ctx.contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.")";
-            $params = array("{$context->path}/%");
-            $records = $DB->get_recordset_sql($sql, $params);
-            foreach ($records as $rec) {
-                $cache->add($rec);
-                $array[$rec->id] = $rec;
+    if ($existing = $DB->get_records_sql($sql, $params)) {
+        foreach($existing as $e) {
+            if ($e->permission == CAP_PROHIBIT) {
+                // prohibit can not be overridden, no point in changing anything
+                return;
             }
-        break;
-
-        case CONTEXT_USER:
-            // Find
-            // - blocks under this context path.
-            $sql = " SELECT ctx.*
-                       FROM {context} ctx
-                      WHERE ctx.path LIKE ?
-                            AND ctx.contextlevel = ".CONTEXT_BLOCK;
-            $params = array("{$context->path}/%", $context->instanceid);
-            $records = $DB->get_recordset_sql($sql, $params);
-            foreach ($records as $rec) {
-                $cache->add($rec);
-                $array[$rec->id] = $rec;
+        }
+        $lowest = array_shift($existing);
+        if ($lowest->permission == $permission) {
+            // permission already set in this context or parent - nothing to do
+            return;
+        }
+        if ($existing) {
+            $parent = array_shift($existing);
+            if ($parent->permission == $permission) {
+                // permission already set in parent context or parent - just unset in this context
+                // we do this because we want as few overrides as possible for performance reasons
+                unassign_capability($capname, $roleid, $context->id);
+                $context->mark_dirty();
+                return;
             }
-            break;
+        }
 
-        case CONTEXT_SYSTEM:
-            // Just get all the contexts except for CONTEXT_SYSTEM level
-            // and hope we don't OOM in the process - don't cache
-            $sql = "SELECT c.*
-                      FROM {context} c
-                     WHERE contextlevel != ".CONTEXT_SYSTEM;
+    } else {
+        if ($permission == CAP_PREVENT) {
+            // nothing means role does not have permission
+            return;
+        }
+    }
 
-            $records = $DB->get_records_sql($sql);
-            foreach ($records as $rec) {
-                $array[$rec->id] = $rec;
-            }
-        break;
+    // assign the needed capability
+    assign_capability($capname, $permission, $roleid, $context->id, true);
 
-        default:
-            print_error('unknowcontext', '', '', $context->contextlevel);
-            return false;
-    }
-    return $array;
+    // force cap reloading
+    $context->mark_dirty();
 }
 
 
 /**
- * Gets a string for sql calls, searching for stuff in this context or above
+ * Context maintenance and helper methods.
  *
- * @param object $context
- * @return string
- */
-function get_related_contexts_string($context) {
-    if ($parents = get_parent_contexts($context)) {
-        return (' IN ('.$context->id.','.implode(',', $parents).')');
-    } else {
-        return (' ='.$context->id);
-    }
-}
-
-/**
- * Returns capability information (cached)
+ * This is "extends context" is a bloody hack that tires to work around the deficiencies
+ * in the "protected" keyword in PHP, this helps us to hide all the internals of context
+ * level implementation from the rest of code, the code completion returns what developers need.
  *
- * @param string $capabilityname
- * @return object or null if capability not found
+ * Thank you Tim Hunt for helping me with this nasty trick.
+ *
+ * @author Petr Skoda
  */
-function get_capability_info($capabilityname) {
-    global $ACCESSLIB_PRIVATE, $DB; // one request per page only
+class context_helper extends context {
 
-    // TODO: cache this in shared memory if available, use new $CFG->roledefrev for version check
+    private static $alllevels = array(
+            CONTEXT_SYSTEM    => 'context_system',
+            CONTEXT_USER      => 'context_user',
+            CONTEXT_COURSECAT => 'context_coursecat',
+            CONTEXT_COURSE    => 'context_course',
+            CONTEXT_MODULE    => 'context_module',
+            CONTEXT_BLOCK     => 'context_block',
+    );
 
-    if (empty($ACCESSLIB_PRIVATE->capabilities)) {
-        $ACCESSLIB_PRIVATE->capabilities = array();
-        $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask');
-        foreach ($caps as $cap) {
-            $capname = $cap->name;
-            unset($cap->id);
-            unset($cap->name);
-            $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap;
+    /**
+     * Instance does not make sense here, only static use
+     */
+    protected function __construct() {
+    }
+
+    /**
+     * Returns a class name of the context level class
+     *
+     * @static
+     * @param int $contextlevel (CONTEXT_SYSTEM, etc.)
+     * @return string class name of the context class
+     */
+    public static function get_class_for_level($contextlevel) {
+        if (isset(self::$alllevels[$contextlevel])) {
+            return self::$alllevels[$contextlevel];
+        } else {
+            throw new coding_exception('Invalid context level specified');
         }
     }
 
-    return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null;
-}
+    /**
+     * Returns a list of all context levels
+     *
+     * @static
+     * @return array int=>string (level=>level class name)
+     */
+    public static function get_all_levels() {
+        return self::$alllevels;
+    }
 
-/**
- * Returns the human-readable, translated version of the capability.
- * Basically a big switch statement.
- *
- * @param string $capabilityname e.g. mod/choice:readresponses
- * @return string
- */
-function get_capability_string($capabilityname) {
+    /**
+     * Remove stale contexts that belonged to deleted instances.
+     * Ideally all code should cleanup contexts properly, unfortunately accidents happen...
+     *
+     * @static
+     * @return void
+     */
+    public static function cleanup_instances() {
+        global $DB;
+        $sqls = array();
+        foreach (self::$alllevels as $level=>$classname) {
+            $sqls[] = $classname::get_cleanup_sql();
+        }
 
-    // Typical capability name is 'plugintype/pluginname:capabilityname'
-    list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
+        $sql = implode(" UNION ", $sqls);
 
-    if ($type === 'moodle') {
-        $component = 'core_role';
-    } else if ($type === 'quizreport') {
-        //ugly hack!!
-        $component = 'quiz_'.$name;
-    } else {
-        $component = $type.'_'.$name;
+        // it is probably better to use transactions, it might be faster too
+        $transaction = $DB->start_delegated_transaction();
+
+        $rs = $DB->get_recordset_sql($sql);
+        foreach ($rs as $record) {
+            $context = context::create_instance_from_record($record);
+            $context->delete();
+        }
+        $rs->close();
+
+        $transaction->allow_commit();
     }
 
-    $stringname = $name.':'.$capname;
+    /**
+     * Create all context instances at the given level and above.
+     *
+     * @static
+     * @param int $contextlevel null means all levels
+     * @param bool $buildpaths
+     * @return void
+     */
+    public static function create_instances($contextlevel = null, $buildpaths = true) {
+        foreach (self::$alllevels as $level=>$classname) {
+            if ($contextlevel and $level > $contextlevel) {
+                // skip potential sub-contexts
+                continue;
+            }
+            $classname::create_level_instances();
+            if ($buildpaths) {
+                $classname::build_paths(false);
+            }
+        }
+    }
 
-    if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
-        return get_string($stringname, $component);
+    /**
+     * Rebuild paths and depths in all context levels.
+     *
+     * @static
+     * @param bool $force false means add missing only
+     * @return void
+     */
+    public static function build_all_paths($force = false) {
+        foreach (self::$alllevels as $classname) {
+            $classname::build_paths($force);
+        }
+
+        // reset static course cache - it might have incorrect cached data
+        accesslib_clear_all_caches(true);
     }
 
-    $dir = get_component_directory($component);
-    if (!file_exists($dir)) {
-        // plugin broken or does not exist, do not bother with printing of debug message
-        return $capabilityname.' ???';
+    /**
+     * Resets the cache to remove all data.
+     * @static
+     */
+    public static function reset_caches() {
+        context::reset_caches();
     }
 
-    // something is wrong in plugin, better print debug
-    return get_string($stringname, $component);
-}
+    /**
+     * Returns all fields necessary for context preloading from user $rec.
+     *
+     * This helps with performance when dealing with hundreds of contexts.
+     *
+     * @static
+     * @param string $tablealias context table alias in the query
+     * @return array (table.column=>alias, ...)
+     */
+    public static function get_preload_record_columns($tablealias) {
+        return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance");
+    }
+
+    /**
+     * Returns all fields necessary for context preloading from user $rec.
+     *
+     * This helps with performance when dealing with hundreds of contexts.
+     *
+     * @static
+     * @param string $tablealias context table alias in the query
+     * @return string
+     */
+    public static function get_preload_record_columns_sql($tablealias) {
+        return "$tablealias.id AS ctxid, $tablealias.path AS ctxpath, $tablealias.depth AS ctxdepth, $tablealias.contextlevel AS ctxlevel, $tablealias.instanceid AS ctxinstance";
+    }
+
+    /**
+     * Preloads context information from db record and strips the cached info.
+     *
+     * The db request has to contain all columns from context_helper::get_preload_record_columns().
+     *
+     * @static
+     * @param stdClass $rec
+     * @return void (modifies $rec)
+     */
+     public static function preload_from_record(stdClass $rec) {
+         context::preload_from_record($rec);
+     }
 
+    /**
+     * Preload all contexts instances from course.
+     *
+     * To be used if you expect multiple queries for course activities...
+     *
+     * @static
+     * @param $courseid
+     */
+    public static function preload_course($courseid) {
+        // Users can call this multiple times without doing any harm
+        if (isset(context::$cache_preloaded[$courseid])) {
+            return;
+        }
+        $coursecontext = context_course::instance($courseid);
+        $coursecontext->get_child_contexts();
 
-/**
- * This gets the mod/block/course/core etc strings.
- *
- * @param string $component
- * @param int $contextlevel
- * @return string|bool String is success, false if failed
- */
-function get_component_string($component, $contextlevel) {
+        context::$cache_preloaded[$courseid] = true;
+    }
 
-    if ($component === 'moodle' or $component === 'core') {
-        switch ($contextlevel) {
-            case CONTEXT_SYSTEM:    return get_string('coresystem');
-            case CONTEXT_USER:      return get_string('users');
-            case CONTEXT_COURSECAT: return get_string('categories');
-            case CONTEXT_COURSE:    return get_string('course');
-            case CONTEXT_MODULE:    return get_string('activities');
-            case CONTEXT_BLOCK:     return get_string('block');
-            default:                print_error('unknowncontext');
+    /**
+     * Delete context instance
+     *
+     * @static
+     * @param int $contextlevel
+     * @param int $instanceid
+     * @return void
+     */
+    public static function delete_instance($contextlevel, $instanceid) {
+        global $DB;
+
+        // double check the context still exists
+        if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
+            $context = context::create_instance_from_record($record);
+            $context->delete();
+        } else {
+            // we should try to purge the cache anyway
         }
     }
 
-    list($type, $name) = normalize_component($component);
-    $dir = get_plugin_directory($type, $name);
-    if (!file_exists($dir)) {
-        // plugin not installed, bad luck, there is no way to find the name
-        return $component.' ???';
+    /**
+     * Returns the name of specified context level
+     *
+     * @static
+     * @param int $contextlevel
+     * @return string name of the context level
+     */
+    public static function get_level_name($contextlevel) {
+        $classname = context_helper::get_class_for_level($contextlevel);
+        return $classname::get_level_name();
     }
 
-    switch ($type) {
-        // TODO this is really hacky
-        case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
-        case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
-        case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
-        case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
-        case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
-        case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
-        case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
-        case 'mod':
-            if (get_string_manager()->string_exists('pluginname', $component)) {
-                return get_string('activity').': '.get_string('pluginname', $component);
-            } else {
-                return get_string('activity').': '.get_string('modulename', $component);
-            }
-        default: return get_string('pluginname', $component);
+    /**
+     * not used
+     */
+    public function get_url() {
+    }
+
+    /**
+     * not used
+     */
+    public function get_capabilities() {
     }
 }
 
+
 /**
- * Gets the list of roles assigned to this context and up (parents)
- * from the list of roles that are visible on user profile page
- * and participants page.
+ * Basic moodle context abstraction class.
  *
- * @param object $context
- * @return array
+ * @author Petr Skoda
+ *
+ * @property-read int $id context id
+ * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc.
+ * @property-read int $instanceid id of related instance in each context
+ * @property-read string $path path to context, starts with system context
+ * @property-read dept $depth
  */
-function get_profile_roles($context) {
-    global $CFG, $DB;
+abstract class context extends stdClass {
 
-    if (empty($CFG->profileroles)) {
-        return array();
-    }
+    /*
+     * Google confirms that no other important framework is using "context" class,
+     * we could use something else like mcontext or moodle_context, but we need to type
+     * this very often which would be annoying and it would take too much space...
+     *
+     * This class is derived from stdClass for backwards compatibility with
+     * odl $context record that was returned from DML $DB->get_record()
+     */
 
-    $allowed = explode(',', $CFG->profileroles);
-    list($rallowed, $params) = $DB->get_in_or_equal($allowed, SQL_PARAMS_NAMED);
+    protected $_id;
+    protected $_contextlevel;
+    protected $_instanceid;
+    protected $_path;
+    protected $_depth;
 
-    $contextlist = get_related_contexts_string($context);
+    /* context caching info */
 
-    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
-              FROM {role_assignments} ra, {role} r
-             WHERE r.id = ra.roleid
-                   AND ra.contextid $contextlist
-                   AND r.id $rallowed
-          ORDER BY r.sortorder ASC";
+    private static $cache_contextsbyid = array();
+    private static $cache_contexts     = array();
+    protected static $cache_count      = 0; // why do we do count contexts? Because count($array) is horribly slow for large arrays
 
-    return $DB->get_records_sql($sql, $params);
-}
+    protected static $cache_preloaded  = array();
+    protected static $systemcontext    = null;
 
-/**
- * Gets the list of roles assigned to this context and up (parents)
- *
- * @param object $context
- * @return array
- */
-function get_roles_used_in_context($context) {
-    global $DB;
+    /**
+     * Resets the cache to remove all data.
+     */
+    protected static function reset_caches() {
+        self::$cache_contextsbyid = array();
+        self::$cache_contexts     = array();
+        self::$cache_count        = 0;
+        self::$cache_preloaded    = array();
 
-    $contextlist = get_related_contexts_string($context);
+        self::$systemcontext = null;
+    }
 
-    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
-              FROM {role_assignments} ra, {role} r
-             WHERE r.id = ra.roleid
-                   AND ra.contextid $contextlist
-          ORDER BY r.sortorder ASC";
+    /**
+     * Adds a context to the cache. If the cache is full, discards a batch of
+     * older entries.
+     *
+     * @static
+     * @param context $context New context to add
+     * @return void
+     */
+    protected static function cache_add(context $context) {
+        if (isset(self::$cache_contextsbyid[$context->id])) {
+            // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
+            return;
+        }
 
-    return $DB->get_records_sql($sql);
-}
+        if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) {
+            $i = 0;
+            foreach(self::$cache_contextsbyid as $ctx) {
+                $i++;
+                if ($i <= 100) {
+                    // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later
+                    continue;
+                }
+                if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
+                    // we remove oldest third of the contexts to make room for more contexts
+                    break;
+                }
+                unset(self::$cache_contextsbyid[$ctx->id]);
+                unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]);
+                self::$cache_count--;
+            }
+        }
 
-/**
- * This function is used to print roles column in user profile page.
- * It is using the CFG->profileroles to limit the list to only interesting roles.
- * (The permission tab has full details of user role assignments.)
- *
- * @param int $userid
- * @param int $courseid
- * @return string
- */
-function get_user_roles_in_course($userid, $courseid) {
-    global $CFG, $DB,$USER;
+        self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context;
+        self::$cache_contextsbyid[$context->id] = $context;
+        self::$cache_count++;
+    }
 
-    if (empty($CFG->profileroles)) {
-        return '';
+    /**
+     * Removes a context from the cache.
+     *
+     * @static
+     * @param context $context Context object to remove
+     * @return void
+     */
+    protected static function cache_remove(context $context) {
+        if (!isset(self::$cache_contextsbyid[$context->id])) {
+            // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
+            return;
+        }
+        unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
+        unset(self::$cache_contextsbyid[$context->id]);
+
+        self::$cache_count--;
+
+        if (self::$cache_count < 0) {
+            self::$cache_count = 0;
+        }
     }
 
-    if ($courseid == SITEID) {
-        $context = get_context_instance(CONTEXT_SYSTEM);
-    } else {
-        $context = get_context_instance(CONTEXT_COURSE, $courseid);
+    /**
+     * Gets a context from the cache.
+     *
+     * @static
+     * @param int $contextlevel Context level
+     * @param int $instance Instance ID
+     * @return context|bool Context or false if not in cache
+     */
+    protected static function cache_get($contextlevel, $instance) {
+        if (isset(self::$cache_contexts[$contextlevel][$instance])) {
+            return self::$cache_contexts[$contextlevel][$instance];
+        }
+        return false;
     }
 
-    if (empty($CFG->profileroles)) {
-        return array();
+    /**
+     * Gets a context from the cache based on its id.
+     *
+     * @static
+     * @param int $id Context ID
+     * @return context|bool Context or false if not in cache
+     */
+    protected static function cache_get_by_id($id) {
+        if (isset(self::$cache_contextsbyid[$id])) {
+            return self::$cache_contextsbyid[$id];
+        }
+        return false;
     }
 
-    $allowed = explode(',', $CFG->profileroles);
-    list($rallowed, $params) = $DB->get_in_or_equal($allowed, SQL_PARAMS_NAMED);
+    /**
+     * Preloads context information from db record and strips the cached info.
+     *
+     * @static
+     * @param stdClass $rec
+     * @return void (modifies $rec)
+     */
+     protected static function preload_from_record(stdClass $rec) {
+         if (empty($rec->ctxid) or empty($rec->ctxlevel) or empty($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) {
+             // $rec does not have enough data, passed here repeatedly or context does not exist yet
+             return;
+         }
 
-    $contextlist = get_related_contexts_string($context);
+         // note: in PHP5 the objects are passed by reference, no need to return $rec
+         $record = new stdClass();
+         $record->id           = $rec->ctxid;       unset($rec->ctxid);
+         $record->contextlevel = $rec->ctxlevel;    unset($rec->ctxlevel);
+         $record->instanceid   = $rec->ctxinstance; unset($rec->ctxinstance);
+         $record->path         = $rec->ctxpath;     unset($rec->ctxpath);
+         $record->depth        = $rec->ctxdepth;    unset($rec->ctxdepth);
 
-    $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
-              FROM {role_assignments} ra, {role} r
-             WHERE r.id = ra.roleid
-                   AND ra.contextid $contextlist
-                   AND r.id $rallowed
-                   AND ra.userid = :userid
-          ORDER BY r.sortorder ASC";
-    $params['userid'] = $userid;
+         return context::create_instance_from_record($record);
+     }
 
-    $rolestring = '';
 
-    if ($roles = $DB->get_records_sql($sql, $params)) {
-        foreach ($roles as $userrole) {
-            $rolenames[$userrole->id] = $userrole->name;
-        }
+    // ====== magic methods =======
 
-        $rolenames = role_fix_names($rolenames, $context);   // Substitute aliases
+    /**
+     * Magic setter method, we do not want anybody to modify properties from the outside
+     * @param string $name
+     * @param mixed @value
+     */
+    public function __set($name, $value) {
+        debugging('Can not change context instance properties!');
+    }
 
-        foreach ($rolenames as $roleid => $rolename) {
-            $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
+    /**
+     * Magic method getter, redirects to read only values.
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name) {
+        switch ($name) {
+            case 'id':           return $this->_id;
+            case 'contextlevel': return $this->_contextlevel;
+            case 'instanceid':   return $this->_instanceid;
+            case 'path':         return $this->_path;
+            case 'depth':        return $this->_depth;
+
+            default:
+                debugging('Invalid context property accessed! '.$name);
+                return null;
         }
-        $rolestring = implode(',', $rolenames);
     }
 
-    return $rolestring;
-}
+    /**
+     * Full support for isset on our magic read only properties.
+     * @param $name
+     * @return bool
+     */
+    public function __isset($name) {
+        switch ($name) {
+            case 'id':           return isset($this->_id);
+            case 'contextlevel': return isset($this->_contextlevel);
+            case 'instanceid':   return isset($this->_instanceid);
+            case 'path':         return isset($this->_path);
+            case 'depth':        return isset($this->_depth);
 
-/**
- * Checks if a user can assign users to a particular role in this context
- *
- * @param object $context
- * @param int $targetroleid - the id of the role you want to assign users to
- * @return boolean
- */
-function user_can_assign($context, $targetroleid) {
-    global $DB;
+            default: return false;
+        }
 
-    // first check if user has override capability
-    // if not return false;
-    if (!has_capability('moodle/role:assign', $context)) {
-        return false;
     }
-    // pull out all active roles of this user from this context(or above)
-    if ($userroles = get_user_roles($context)) {
-        foreach ($userroles as $userrole) {
-            // if any in the role_allow_override table, then it's ok
-            if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
-                return true;
-            }
-        }
+
+    /**
+     * ALl properties are read only, sorry.
+     * @param string $name
+     */
+    public function __unset($name) {
+        debugging('Can not unset context instance properties!');
     }
 
-    return false;
-}
+    // ====== general context methods ======
 
-/**
- * Returns all site roles in correct sort order.
- *
- * @return array
- */
-function get_all_roles() {
-    global $DB;
-    return $DB->get_records('role', null, 'sortorder ASC');
-}
+    /**
+     * Constructor is protected so that devs are forced to
+     * use context_xxx::instance() or context::instance_by_id().
+     *
+     * @param stdClass $record
+     */
+    protected function __construct(stdClass $record) {
+        $this->_id           = $record->id;
+        $this->_contextlevel = (int)$record->contextlevel;
+        $this->_instanceid   = $record->instanceid;
+        $this->_path         = $record->path;
+        $this->_depth        = $record->depth;
+    }
 
-/**
- * Returns roles of a specified archetype
- * @param string $archetype
- * @return array of full role records
- */
-function get_archetype_roles($archetype) {
-    global $DB;
-    return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
-}
+    /**
+     * This function is also used to work around 'protected' keyword problems in context_helper.
+     * @param stdClass $record
+     * @return context instance
+     */
+    protected static function create_instance_from_record(stdClass $record) {
+        $classname = context_helper::get_class_for_level($record->contextlevel);
 
-/**
- * Gets all the user roles assigned in this context, or higher contexts
- * this is mainly used when checking if a user can assign a role, or overriding a role
- * i.e. we need to know what this user holds, in order to verify against allow_assign and
- * allow_override tables
- *
- * @param object $context
- * @param int $userid
- * @param bool $checkparentcontexts defaults to true
- * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
- * @return array
- */
-function get_user_roles($context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
-    global $USER, $DB;
+        if ($context = context::cache_get_by_id($record->id)) {
+            return $context;
+        }
 
-    if (empty($userid)) {
-        if (empty($USER->id)) {
-            return array();
+        $context = new $classname($record);
+        context::cache_add($context);
+
+        return $context;
+    }
+
+    /**
+     * Copy prepared new contexts from temp table to context table,
+     * we do this in db specific way for perf reasons only.
+     * @static
+     */
+    protected static function merge_context_temp_table() {
+        global $DB;
+
+        /* MDL-11347:
+         *  - mysql does not allow to use FROM in UPDATE statements
+         *  - using two tables after UPDATE works in mysql, but might give unexpected
+         *    results in pg 8 (depends on configuration)
+         *  - using table alias in UPDATE does not work in pg < 8.2
+         *
+         * Different code for each database - mostly for performance reasons
+         */
+
+        $dbfamily = $DB->get_dbfamily();
+        if ($dbfamily == 'mysql') {
+            $updatesql = "UPDATE {context} ct, {context_temp} temp
+                             SET ct.path     = temp.path,
+                                 ct.depth    = temp.depth
+                           WHERE ct.id = temp.id";
+        } else if ($dbfamily == 'oracle') {
+            $updatesql = "UPDATE {context} ct
+                             SET (ct.path, ct.depth) =
+                                 (SELECT temp.path, temp.depth
+                                    FROM {context_temp} temp
+                                   WHERE temp.id=ct.id)
+                           WHERE EXISTS (SELECT 'x'
+                                           FROM {context_temp} temp
+                                           WHERE temp.id = ct.id)";
+        } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
+            $updatesql = "UPDATE {context}
+                             SET path     = temp.path,
+                                 depth    = temp.depth
+                            FROM {context_temp} temp
+                           WHERE temp.id={context}.id";
+        } else {
+            // sqlite and others
+            $updatesql = "UPDATE {context}
+                             SET path     = (SELECT path FROM {context_temp} WHERE id = {context}.id),
+                                 depth    = (SELECT depth FROM {context_temp} WHERE id = {context}.id)
+                             WHERE id IN (SELECT id FROM {context_temp})";
         }
-        $userid = $USER->id;
-    }
 
-    if ($checkparentcontexts) {
-        $contextids = get_parent_contexts($context);
-    } else {
-        $contextids = array();
+        $DB->execute($updatesql);
     }
-    $contextids[] = $context->id;
 
-    list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
+   /**
+    * Get a context instance as an object, from a given context id.
+    *
+    * @static
+    * @param int $id context id
+    * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
+    *                        MUST_EXIST means throw exception if no record found
+    * @return context|bool the context object or false if not found
+    */
+    public static function instance_by_id($id, $strictness = MUST_EXIST) {
+        global $DB;
 
-    array_unshift($params, $userid);
+        if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') {
+            // some devs might confuse context->id and instanceid, better prevent these mistakes completely
+            throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
+        }
 
-    $sql = "SELECT ra.*, r.name, r.shortname
-              FROM {role_assignments} ra, {role} r, {context} c
-             WHERE ra.userid = ?
-                   AND ra.roleid = r.id
-                   AND ra.contextid = c.id
-                   AND ra.contextid $contextids
-          ORDER BY $order";
+        if ($id == SYSCONTEXTID) {
+            return context_system::instance(0, $strictness);
+        }
 
-    return $DB->get_records_sql($sql ,$params);
-}
+        if (is_array($id) or is_object($id) or empty($id)) {
+            throw new coding_exception('Invalid context id specified context::instance_by_id()');
+        }
 
-/**
- * Creates a record in the role_allow_override table
- *
- * @param int $sroleid source roleid
- * @param int $troleid target roleid
- * @return void
- */
-function allow_override($sroleid, $troleid) {
-    global $DB;
+        if ($context = context::cache_get_by_id($id)) {
+            return $context;
+        }
 
-    $record = new stdClass();
-    $record->roleid        = $sroleid;
-    $record->allowoverride = $troleid;
-    $DB->insert_record('role_allow_override', $record);
-}
+        if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
+            return context::create_instance_from_record($record);
+        }
 
-/**
- * Creates a record in the role_allow_assign table
- *
- * @param int $sroleid source roleid
- * @param int $troleid target roleid
- * @return void
- */
-function allow_assign($fromroleid, $targetroleid) {
-    global $DB;
+        return false;
+    }
 
-    $record = new stdClass();
-    $record->roleid      = $fromroleid;
-    $record->allowassign = $targetroleid;
-    $DB->insert_record('role_allow_assign', $record);
-}
+    /**
+     * Update context info after moving context in the tree structure.
+     *
+     * @param context $newparent
+     * @return void
+     */
+    public function update_moved(context $newparent) {
+        global $DB;
 
-/**
- * Creates a record in the role_allow_switch table
- *
- * @param int $sroleid source roleid
- * @param int $troleid target roleid
- * @return void
- */
-function allow_switch($fromroleid, $targetroleid) {
-    global $DB;
+        $frompath = $this->_path;
+        $newpath  = $newparent->path . '/' . $this->_id;
 
-    $record = new stdClass();
-    $record->roleid      = $fromroleid;
-    $record->allowswitch = $targetroleid;
-    $DB->insert_record('role_allow_switch', $record);
-}
+        $trans = $DB->start_delegated_transaction();
 
-/**
- * Gets a list of roles that this user can assign in this context
- *
- * @param object $context the context.
- * @param int $rolenamedisplay the type of role name to display. One of the
- *      ROLENAME_X constants. Default ROLENAME_ALIAS.
- * @param bool $withusercounts if true, count the number of users with each role.
- * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
- * @return array if $withusercounts is false, then an array $roleid => $rolename.
- *      if $withusercounts is true, returns a list of three arrays,
- *      $rolenames, $rolecounts, and $nameswithcounts.
- */
-function get_assignable_roles($context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
-    global $USER, $DB;
+        $this->mark_dirty();
 
-    // make sure there is a real user specified
-    if ($user === null) {
-        $userid = isset($USER->id) ? $USER->id : 0;
-    } else {<