Merge branch 'MDL-49398_final4' of https://github.com/sk-unikent/moodle

This commit is contained in:
Jun Pataleta 2017-06-06 12:43:14 +08:00
commit c9a3a21c31
8 changed files with 254 additions and 450 deletions

View File

@ -5154,9 +5154,12 @@ class api {
$syscontext = context_system::instance();
$hassystem = has_capability($capability, $syscontext, $userid);
$access = get_user_access_sitewide($userid);
$access = get_user_roles_sitewide_accessdata($userid);
// Build up a list of level 2 contexts (candidates to be user context).
$filtercontexts = array();
// Build list of roles to check overrides.
$roles = array();
foreach ($access['ra'] as $path => $role) {
$parts = explode('/', $path);
if (count($parts) == 3) {
@ -5165,24 +5168,23 @@ class api {
// We know this is not a user context because there is another path with more than 2 levels.
unset($filtercontexts[$parts[2]]);
}
$roles = array_merge($roles, $role);
}
// Add all contexts in which a role may be overidden.
foreach ($access['rdef'] as $pathandroleid => $def) {
$matches = array();
if (!isset($def[$capability])) {
// The capability is not mentioned, we can ignore.
continue;
$rdefs = get_role_definitions($roles);
foreach ($rdefs as $roledef) {
foreach ($roledef as $path => $caps) {
if (!isset($caps[$capability])) {
// The capability is not mentioned, we can ignore.
continue;
}
$parts = explode('/', $path);
if (count($parts) === 3) {
// Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
$filtercontexts[$parts[2]] = $parts[2];
}
}
list($contextpath, $roleid) = explode(':', $pathandroleid, 2);
$parts = explode('/', $contextpath);
if (count($parts) != 3) {
// Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
continue;
}
$filtercontexts[$parts[2]] = $parts[2];
}
// No interesting contexts - return all or no results.

View File

@ -63,6 +63,7 @@ $string['cachedef_plugin_manager'] = 'Plugin info manager';
$string['cachedef_tagindexbuilder'] = 'Search results for tagged items';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_roledefs'] = 'Role definitions';
$string['cachedef_grade_categories'] = 'Grade category queries';
$string['cachedef_string'] = 'Language string cache';
$string['cachedef_tags'] = 'Tags collections and areas';

View File

@ -56,9 +56,7 @@
* - load_all_capabilities()
* - reload_all_capabilities()
* - has_capability_in_accessdata()
* - get_user_access_sitewide()
* - load_course_context()
* - load_role_access_by_context()
* - get_user_roles_sitewide_accessdata()
* - etc.
*
* <b>Name conventions</b>
@ -86,24 +84,6 @@
* [$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
* </code>
*
* See how has_capability_in_accessdata() walks up the tree.
*
* 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($courseid1=>1, $courseid2=>1)
* </code>
*
* <b>Stale accessdata</b>
*
* For the logged-in user, accessdata is long-lived.
@ -200,9 +180,9 @@ if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
*/
global $ACCESSLIB_PRIVATE;
$ACCESSLIB_PRIVATE = new stdClass();
$ACCESSLIB_PRIVATE->cacheroledefs = array(); // Holds site-wide role definitions.
$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
/**
* Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
@ -239,7 +219,10 @@ function accesslib_clear_all_caches($resetcontexts) {
$ACCESSLIB_PRIVATE->dirtycontexts = null;
$ACCESSLIB_PRIVATE->accessdatabyuser = array();
$ACCESSLIB_PRIVATE->rolepermissions = array();
$ACCESSLIB_PRIVATE->cacheroledefs = array();
$cache = cache::make('core', 'roledefs');
$cache->purge();
if ($resetcontexts) {
context_helper::reset_caches();
@ -247,71 +230,113 @@ function accesslib_clear_all_caches($resetcontexts) {
}
/**
* Gets the accessdata for role "sitewide" (system down to course)
* Clears accesslib's private cache of a specific role or roles. ONLY BE USED FROM THIS LIBRARY FILE!
*
* This reset does not touch global $USER.
*
* @access private
* @param int|array $roles
* @return void
*/
function accesslib_clear_role_cache($roles) {
global $ACCESSLIB_PRIVATE;
if (!is_array($roles)) {
$roles = [$roles];
}
foreach ($roles as $role) {
if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
}
}
$cache = cache::make('core', 'roledefs');
$cache->delete_many($roles);
}
/**
* Role is assigned at system context.
*
* @access private
* @param int $roleid
* @return array
*/
function get_role_access($roleid) {
global $DB, $ACCESSLIB_PRIVATE;
/* Get it in 1 DB query...
* - relevant role caps at the root and down
* to the course level - but not below
*/
//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 down to COURSE - not below -.
/*
$sql = "SELECT ctx.path,
rc.capability, rc.permission
FROM {context} ctx
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);
*/
// Note: the commented out query is 100% accurate but slow, so let's cheat instead by hardcoding the blocks mess directly.
$sql = "SELECT COALESCE(ctx.path, bctx.path) AS path, rc.capability, rc.permission
FROM {role_capabilities} rc
LEFT JOIN {context} ctx ON (ctx.id = rc.contextid AND ctx.contextlevel <= ".CONTEXT_COURSE.")
LEFT JOIN ({context} bctx
JOIN {block_instances} bi ON (bi.id = bctx.instanceid)
JOIN {context} pctx ON (pctx.id = bi.parentcontextid AND pctx.contextlevel < ".CONTEXT_COURSE.")
) ON (bctx.id = rc.contextid AND bctx.contextlevel = ".CONTEXT_BLOCK.")
WHERE rc.roleid = :roleid AND (ctx.id IS NOT NULL OR bctx.id IS NOT NULL)";
$params = array('roleid'=>$roleid);
// we need extra caching in CLI scripts and cron
$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();
// share the role definitions
foreach ($accessdata['rdef'] as $k=>$unused) {
if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
$ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
}
$accessdata['rdef_count']++;
$accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
}
return $accessdata;
}
/**
* Fetch raw "site wide" role definitions.
* Even MUC static acceleration cache appears a bit slow for this.
* Important as can be hit hundreds of times per page.
*
* @param array $roleids List of role ids to fetch definitions for.
* @return array Complete definition for each requested role.
*/
function get_role_definitions(array $roleids) {
global $ACCESSLIB_PRIVATE;
if (empty($roleids)) {
return array();
}
// Grab all keys we have not yet got in our static cache.
if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
$cache = cache::make('core', 'roledefs');
$ACCESSLIB_PRIVATE->cacheroledefs += array_filter($cache->get_many($uncached));
// Check we have the remaining keys from the MUC.
if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
$uncached = get_role_definitions_uncached($uncached);
$ACCESSLIB_PRIVATE->cacheroledefs += $uncached;
$cache->set_many($uncached);
}
}
// Return just the roles we need.
return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
}
/**
* Query raw "site wide" role definitions.
*
* @param array $roleids List of role ids to fetch definitions for.
* @return array Complete definition for each requested role.
*/
function get_role_definitions_uncached(array $roleids) {
global $DB;
if (empty($roleids)) {
return array();
}
list($sql, $params) = $DB->get_in_or_equal($roleids);
$rdefs = array();
$sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
FROM {role_capabilities} rc
JOIN {context} ctx ON rc.contextid = ctx.id
WHERE rc.roleid $sql
ORDER BY ctx.path, rc.roleid, rc.capability";
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $rd) {
if (!isset($rdefs[$rd->roleid][$rd->path])) {
if (!isset($rdefs[$rd->roleid])) {
$rdefs[$rd->roleid] = array();
}
$rdefs[$rd->roleid][$rd->path] = array();
}
$rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
}
$rs->close();
return $rdefs;
}
/**
* Get the default guest role, this is used for guest account,
* search engine spiders, etc.
@ -487,13 +512,6 @@ function has_capability($capability, context $context, $user = null, $doanything
$access =& $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, $access);
}
@ -742,11 +760,13 @@ function has_capability_in_accessdata($capability, context $context, array &$acc
}
// Now find out what access is given to each role, going bottom-->up direction
$rdefs = get_role_definitions(array_keys($roles));
$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 (isset($rdefs[$roleid][$path][$capability])) {
$perm = (int)$rdefs[$roleid][$path][$capability];
if ($perm === CAP_PROHIBIT) {
// any CAP_PROHIBIT found means no permission for the user
return false;
@ -790,39 +810,22 @@ function require_capability($capability, context $context, $userid = null, $doan
}
/**
* 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 can be HUGE.
*
* [ra] => [/path][roleid]=roleid
* [rdef] => [/path:roleid][capability]=permission
* Return a nested array showing all role assignments for the user.
* [ra] => [contextpath][roleid] = roleid
*
* @access private
* @param int $userid - the id of the user
* @return array access info array
*/
function get_user_access_sitewide($userid) {
global $CFG, $DB, $ACCESSLIB_PRIVATE;
function get_user_roles_sitewide_accessdata($userid) {
global $CFG, $DB;
/* 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
*/
// raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef
$raparents = array();
$accessdata = get_empty_accessdata();
// 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->id] = $syscontext->id;
}
// load the "default frontpage role"
@ -830,258 +833,27 @@ function get_user_access_sitewide($userid) {
$frontpagecontext = context_course::instance(get_site()->id);
if ($frontpagecontext->path) {
$accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
$raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id;
}
}
// preload every assigned role at and above course context
// Preload every assigned role.
$sql = "SELECT ctx.path, ra.roleid, ra.contextid
FROM {role_assignments} ra
JOIN {context} ctx
ON ctx.id = ra.contextid
LEFT JOIN {block_instances} bi
ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
LEFT JOIN {context} bpctx
ON (bpctx.id = bi.parentcontextid)
WHERE ra.userid = :userid
AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")";
$params = array('userid'=>$userid);
$rs = $DB->get_recordset_sql($sql, $params);
JOIN {context} ctx ON ctx.id = ra.contextid
WHERE ra.userid = :userid";
$rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
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->contextid] = $ra->contextid;
}
$rs->close();
if (empty($raparents)) {
return $accessdata;
}
// 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 at/above course context...
$sqls = array();
$params = array();
static $cp = 0;
foreach ($raparents as $roleid=>$ras) {
$cp++;
list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_');
$params = array_merge($params, $cids);
$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)
JOIN {context} pctx
ON (pctx.id $sqlcids
AND (ctx.id = pctx.id
OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")."
OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'")."))
LEFT JOIN {block_instances} bi
ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
LEFT JOIN {context} bpctx
ON (bpctx.id = bi.parentcontextid)
WHERE rc.roleid = :r{$cp}
AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")
)";
}
// fixed capability order is necessary for rdef dedupe
$rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params);
foreach ($rs as $rd) {
$k = $rd->path.':'.$rd->roleid;
$accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
}
$rs->close();
// share the role definitions
foreach ($accessdata['rdef'] as $k=>$unused) {
if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
$ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
}
$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 course.
*
* This function injects all course related access info into the accessdata array.
*
* @access 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_course_context($userid, context_course $coursecontext, &$accessdata) {
global $DB, $CFG, $ACCESSLIB_PRIVATE;
if (empty($coursecontext->path)) {
// weird, this should not happen
return;
}
if (isset($accessdata['loaded'][$coursecontext->instanceid])) {
// already loaded, great!
return;
}
$roles = array();
if (empty($userid)) {
if (!empty($CFG->notloggedinroleid)) {
$roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid;
}
} else if (isguestuser($userid)) {
if ($guestrole = get_guest_role()) {
$roles[$guestrole->id] = $guestrole->id;
}
} 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) {
$accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
$roles[$ra->roleid] = $ra->roleid;
}
$rs->close();
// 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 not forget the default role
if (!empty($CFG->defaultuserroleid)) {
$roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
}
}
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('pathprefix' => $coursecontext->path . '/%');
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 {context} ctx
JOIN {role_capabilities} rc ON rc.contextid = ctx.id
WHERE rc.roleid $roleids
AND (ctx.id $parentsaself OR ctx.path LIKE :pathprefix)
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.':'.$rd->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];
}
$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 temporary course roles.
*
* @access private
* @param int $roleid the id of the user
* @param context $context needs path!
* @param array $accessdata accessdata array (is modified)
* @return array
*/
function load_role_access_by_context($roleid, context $context, &$accessdata) {
global $DB, $ACCESSLIB_PRIVATE;
/* Get the relevant rolecaps into rdef
* - relevant role caps
* - at ctx and above
* - below this ctx
*/
if (empty($context->path)) {
// weird, this should not happen
return;
}
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.'/%';
$sql = "SELECT ctx.path, rc.capability, rc.permission
FROM {role_capabilities} rc
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;
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.
*
@ -1091,10 +863,6 @@ function load_role_access_by_context($roleid, context $context, &$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();
$accessdata['rsw'] = array();
@ -1112,11 +880,7 @@ function get_empty_accessdata() {
function get_user_accessdata($userid, $preloadonly=false) {
global $CFG, $ACCESSLIB_PRIVATE, $USER;
if (!empty($USER->access['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) {
// share rdef from USER session with rolepermissions cache in order to conserve memory
foreach ($USER->access['rdef'] as $k=>$v) {
$ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->access['rdef'][$k];
}
if (isset($USER->access)) {
$ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
}
@ -1138,7 +902,8 @@ function get_user_accessdata($userid, $preloadonly=false) {
}
} else {
$accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role
// Includes default role and frontpage role.
$accessdata = get_user_roles_sitewide_accessdata($userid);
}
$ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
@ -1151,45 +916,6 @@ function get_user_accessdata($userid, $preloadonly=false) {
}
}
/**
* 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.
*
* @access private
* @return void
*/
function dedupe_user_access() {
global $USER;
if (CLI_SCRIPT) {
// no session in CLI --> no compression necessary
return;
}
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];
}
}
$USER->access['rdef_lcc'] = $USER->access['rdef_count'];
}
/**
* A convenience function to completely load all the capabilities
* for the current user. It is called from has_capability() and functions change permissions.
@ -1216,9 +942,6 @@ function load_all_capabilities() {
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($USER->mycourses);
@ -1296,12 +1019,7 @@ function load_temp_course_role(context_course $coursecontext, $roleid) {
return;
}
// 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);
}
/**
@ -1536,6 +1254,9 @@ function delete_role($roleid) {
$event->add_record_snapshot('role', $role);
$event->trigger();
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
return true;
}
@ -1587,6 +1308,10 @@ function assign_capability($capability, $permission, $roleid, $contextid, $overw
$DB->insert_record('role_capabilities', $cap);
}
}
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
return true;
}
@ -1614,6 +1339,10 @@ function unassign_capability($capability, $roleid, $contextid = null) {
} else {
$DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
}
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
return true;
}
@ -2341,6 +2070,9 @@ function reset_role_capabilities($roleid) {
assign_capability($cap, $permission, $roleid, $systemcontext->id);
}
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
// Mark the system context dirty.
context_system::instance()->mark_dirty();
}
@ -4099,27 +3831,8 @@ function get_roles_on_exact_context(context $context) {
function role_switch($roleid, context $context) {
global $USER;
//
// 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])
// Add the ghost RA to $USER->access as $USER->access['rsw'][$path] = $roleid.
// To un-switch just unset($USER->access['rsw'][$path]).
//
// Note: it is not possible to switch to roles that do not have course:view
@ -4127,7 +3840,6 @@ function role_switch($roleid, context $context) {
load_all_capabilities();
}
// Add the switch RA
if ($roleid == 0) {
unset($USER->access['rsw'][$context->path]);
@ -4136,9 +3848,6 @@ function role_switch($roleid, context $context) {
$USER->access['rsw'][$context->path] = $roleid;
// Load roledefs
load_role_access_by_context($roleid, $context, $USER->access);
return true;
}
@ -4545,6 +4254,9 @@ function role_cap_duplicate($sourcerole, $targetrole) {
$cap->roleid = $targetrole;
$DB->insert_record('role_capabilities', $cap);
}
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($targetrole);
}
/**
@ -5279,11 +4991,18 @@ abstract class context extends stdClass implements IteratorAggregate {
require_once($CFG->dirroot.'/grade/grading/lib.php');
grading_manager::delete_all_for_context($this->_id);
$ids = $DB->get_fieldset_select('role_capabilities', 'DISTINCT roleid', 'contextid = ?', array($this->_id));
// 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'=>$this->_id));
$DB->delete_records('role_capabilities', array('contextid'=>$this->_id));
$DB->delete_records('role_names', array('contextid'=>$this->_id));
if ($ids) {
// Reset any cache of these roles, including MUC.
accesslib_clear_role_cache($ids);
}
}
/**

View File

@ -247,6 +247,15 @@ $definitions = array(
'simpledata' => true,
),
// Cache system-wide role definitions.
'roledefs' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 30,
),
// Caches plugins existing functions by function name and file.
// Set static acceleration size to 5 to load a few functions.
'plugin_functions' => array(

View File

@ -6569,3 +6569,77 @@ function calendar_cron() {
return true;
}
/**
* Previous internal API, it was not supposed to be used anywhere.
*
* @access private
* @deprecated since Moodle 3.4 and removed immediately. MDL-49398.
* @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_course_context($userid, context_course $coursecontext, &$accessdata) {
throw new coding_exception('load_course_context() is removed. Do not use private functions or data structures.');
}
/**
* Previous internal API, it was not supposed to be used anywhere.
*
* @access private
* @deprecated since Moodle 3.4 and removed immediately. MDL-49398.
* @param int $roleid the id of the user
* @param context $context needs path!
* @param array $accessdata accessdata array (is modified)
* @return array
*/
function load_role_access_by_context($roleid, context $context, &$accessdata) {
throw new coding_exception('load_role_access_by_context() is removed. Do not use private functions or data structures.');
}
/**
* Previous internal API, it was not supposed to be used anywhere.
*
* @access private
* @deprecated since Moodle 3.4 and removed immediately. MDL-49398.
* @return void
*/
function dedupe_user_access() {
throw new coding_exception('dedupe_user_access() is removed. Do not use private functions or data structures.');
}
/**
* Previous internal API, it was not supposed to be used anywhere.
* Return a nested array showing role assignments
* and all relevant role capabilities for the user.
*
* [ra] => [/path][roleid]=roleid
* [rdef] => ["$contextpath:$roleid"][capability]=permission
*
* @access private
* @deprecated since Moodle 3.4. MDL-49398.
* @param int $userid - the id of the user
* @return array access info array
*/
function get_user_access_sitewide($userid) {
debugging('get_user_access_sitewide() is deprecated. Do not use private functions or data structures.', DEBUG_DEVELOPER);
$accessdata = get_user_accessdata($userid);
$accessdata['rdef'] = array();
$roles = array();
foreach ($accessdata['ra'] as $path => $pathroles) {
$roles = array_merge($pathroles, $roles);
}
$rdefs = get_role_definitions($roles);
foreach ($rdefs as $roleid => $rdef) {
foreach ($rdef as $path => $caps) {
$accessdata['rdef']["$path:$roleid"] = $caps;
}
}
return $accessdata;
}

View File

@ -57,12 +57,8 @@ class core_accesslib_testcase extends advanced_testcase {
$this->setAdminUser();
load_all_capabilities();
$this->assertNotEmpty($ACCESSLIB_PRIVATE->rolepermissions);
$this->assertNotEmpty($ACCESSLIB_PRIVATE->rolepermissions);
$this->assertNotEmpty($ACCESSLIB_PRIVATE->accessdatabyuser);
accesslib_clear_all_caches_for_unit_testing();
$this->assertEmpty($ACCESSLIB_PRIVATE->rolepermissions);
$this->assertEmpty($ACCESSLIB_PRIVATE->rolepermissions);
$this->assertEmpty($ACCESSLIB_PRIVATE->dirtycontexts);
$this->assertEmpty($ACCESSLIB_PRIVATE->accessdatabyuser);
}
@ -93,9 +89,9 @@ class core_accesslib_testcase extends advanced_testcase {
$this->assertTrue(is_array($access));
$this->assertTrue(is_array($access['ra']));
$this->assertTrue(is_array($access['rdef']));
$this->assertTrue(isset($access['rdef_count']));
$this->assertTrue(is_array($access['loaded']));
$this->assertFalse(isset($access['rdef']));
$this->assertFalse(isset($access['rdef_count']));
$this->assertFalse(isset($access['loaded']));
$this->assertTrue(isset($access['time']));
$this->assertTrue(is_array($access['rsw']));
}

View File

@ -7,6 +7,9 @@ information provided here is intended especially for developers.
allow users to define a list of file types; either by typing them manually or selecting them from a list. The widgets
directly support the syntax used to feed the 'accepted_types' option of the filemanager and filepicker elements. File
types can be specified as extensions (.jpg or just jpg), mime types (text/plain) or groups (image).
* Removed accesslib private functions: load_course_context(), load_role_access_by_context(), dedupe_user_access() (MDL-49398).
* Internal "accessdata" structure format has changed to improve ability to perform role definition caching (MDL-49398).
* Role definitions are no longer cached in user session (MDL-49398).
=== 3.3.1 ===

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017060500.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017060600.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.