accesslib: has_capability() now works for the logged in user

With this commit, the new has_capability works for the logged
in user correctly.

 - load_all_capabilities() no longer calls load_user_capability()!
   6K dbqueries less on login... ;-)

 - When delving into a context we haven't loaded yet, we call
   get_user_access_bycontext()

 - Introduce: get_user_access_bycontext()

 - Several fixes in get_user_access_sitewide()
   (renamed from get_user_sitewide_access())

 - Fixes in has_cap_fromsess()

 - Introduce access_insess() to check if we have to call
   get_user_access_bycontext() for the context
This commit is contained in:
martinlanghoff
2007-09-19 07:02:44 +00:00
parent 7f97ea29bf
commit 74ac5b66cb
+234 -22
View File
@@ -358,27 +358,52 @@ function has_capability($capability, $context=NULL, $userid=NULL, $doanything=tr
$context = $CONTEXT;
}
}
if (empty($CONTEXT)) {
$CONTEXT = $context;
}
if (is_null($userid) || $userid===0) {
$userid = $USER->id;
}
//error_log(print_r($context,1));
$contexts = array();
if ($context->path === '') {
$contexts(SITECONTEXTID, $context->id);
if (empty($context->path)) {
$contexts[] = SYSCONTEXTID;
$context->path = '/' . SYSCONTEXTID;
if (isset($context->id) && $context->id ==! SYSCONTEXTID) {
$contexts[] = $context->id;
$context->path .= '/' . $context->id;
}
} else {
$contexts = explode('/', $context->path);
array_shift($contexts);
}
if ($USER->id === $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_cap_fromsess($capability, $context, $USER->access, $doanything);
}
///$coursepath = get_course_from_path($context->path);
/// if ($USER->access) {
/// $USER->access['courses'] = get_course_access($context, $userid);
// Load it as needed
if (!access_insess($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']}");
$USER->access = get_user_access_bycontext($USER->id, $context,
$USER->access);
}
return has_cap_fromsess($capability, $context, $USER->access, $doanything);
}
error_log("not implemented $userid $capability {$context->contextlevel} {$context->path} ");
@@ -403,6 +428,27 @@ function get_course_from_path ($path) {
return false;
}
function access_insess($path, $sess) {
// 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, $sess['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, $sess['loaded'], true)) {
return true;
}
}
return false;
}
function has_cap_fromsess($capability, $context, $sess, $doanything) {
$path = $context->path;
@@ -420,17 +466,15 @@ function has_cap_fromsess($capability, $context, $sess, $doanything) {
// From the bottom up...
for ($n=$cc-1;$n>=0;$n--) {
$ctxp = $contexts[$n];
if (isset($USER->access['ra'][$ctxp])) {
if (isset($sess['ra'][$ctxp])) {
// Found a role assignment
$roleid = $sess['ra'][$ctxp];
error_log("found ra $roleid for $ctxp");
// Walk the path for capabilities
// from the bottom up...
for ($m=$cc-1;$m>=0;$m--) {
$capctxp = $contexts[$m];
if (isset($sess['rdef']["{$capctxp}:$roleid"][$capability])) {
$perm = $sess['rdef']["{$capctxp}:$roleid"][$capability];
error_log("found rc for $roleid for $ctxp in {$capctxp}:$roleid $capability $perm");
if ($perm === CAP_PROHIBIT) {
return false;
} else {
@@ -1277,7 +1321,7 @@ function load_user_capability($capability='', $context=NULL, $userid=NULL, $chec
}
/**
* It will return 2 arrays showing role assignments
* It will return a nested array showing role assignments
* all relevant role capabilities for the user at
* site/metacourse/course_category/course levels
*
@@ -1286,11 +1330,12 @@ function load_user_capability($capability='', $context=NULL, $userid=NULL, $chec
*
* [ra] => [/path/] = roleid
* [rdef] => [/path/:roleid][capability]=permission
* [loaded] => array('/path', '/path')
*
* @param $userid integer - the id of the user
*
*/
function get_user_sitewide_access($userid) {
function get_user_access_sitewide($userid) {
global $CFG;
@@ -1307,9 +1352,10 @@ function get_user_sitewide_access($userid) {
* - below this user's RAs - limited to course level
*/
$sw = array(); // named list
$sw['ra'] = array();
$sw['rdef'] = array();
$acc = array(); // named list
$acc['ra'] = array();
$acc['rdef'] = array();
$acc['loaded'] = array();
$sitectx = get_field('context', 'id','contextlevel', CONTEXT_SYSTEM);
$base = "/$sitectx";
@@ -1334,10 +1380,10 @@ function get_user_sitewide_access($userid) {
$raparents = array();
if ($rs->RecordCount()) {
while ($ra = rs_fetch_next_record($rs)) {
$sw['ra'][$ra->path] = $ra->roleid;
$acc['ra'][$ra->path] = $ra->roleid;
if (!empty($ra->capability)) {
$k = "{$ra->path}:{$ra->roleid}";
$sw['rdef'][$k][$ra->capability] = $ra->permission;
$acc['rdef'][$k][$ra->capability] = $ra->permission;
}
$parentids = explode('/', $ra->path);
array_pop($parentids); array_shift($parentids);
@@ -1347,6 +1393,7 @@ function get_user_sitewide_access($userid) {
$raparents[$ra->roleid] = $parentids;
}
}
unset($ra);
}
rs_close($rs);
@@ -1376,8 +1423,9 @@ function get_user_sitewide_access($userid) {
if ($rs->RecordCount()) {
while ($rd = rs_fetch_next_record($rs)) {
$k = "{$rd->path}:{$rd->roleid}";
$sw['rdef'][$k][$rd->capability] = $rd->permission;
$acc['rdef'][$k][$rd->capability] = $rd->permission;
}
unset($rd);
}
rs_close($rs);
@@ -1401,20 +1449,184 @@ function get_user_sitewide_access($userid) {
ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
WHERE ra.userid = $userid
AND sctx.contextlevel <= ".CONTEXT_COURSE."
ORDER BY sctx.depth, sctx.path, $ra.roleid";
ORDER BY sctx.depth, sctx.path, ra.roleid";
if ($rs->RecordCount()) {
while ($rd = rs_fetch_next_record($rs)) {
$k = "{$rd->path}:{$rd->roleid}";
$sw['rdef'][$k][$rd->capability] = $rd->permission;
$acc['rdef'][$k][$rd->capability] = $rd->permission;
}
unset($rd);
}
rs_close($rs);
// TODO: compact capsets?
return $acc;
}
/**
* It add to the access ctrl array the data
* needed for a given context
*
* @param $userid integer - the id of the user
* @param $context context obj - needs path!
* @param $acc access array
*
*/
function get_user_access_bycontext($userid, $context, $acc=NULL) {
global $CFG;
/* Get in 3 cheap DB queries...
* - role assignments - with role_caps
* - relevant role caps
* - above this user's RAs
* - below this user's RAs - limited to course level
*/
if (is_null($acc)) {
$acc = array(); // named list
$acc['ra'] = array();
$acc['rdef'] = array();
$acc['loaded'] = array();
}
$base = "/" . SYSCONTEXTID;
// Determine the course context we'll go
// after, though we are usually called
// with a lower ctx. We have 3 easy 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.
// ... for now!
//
$targetpath;
$targetlevel;
if ($context->contextlevel === CONTEXT_COURSE) {
$targetpath = $context->path;
$targetlevel = $context->contextlevel;
} elseif ($context->path === "$base/{$context->id}") {
$targetpath = $context->path;
$targetlevel = $context->contextlevel;
} else {
// 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
$targetpath = get_course_from_path($context->path);
$targetlevel = CONTEXT_COURSE;
}
//
// Role assignments in the context and below - and any rolecaps directly linked
// because it's cheap to read rolecaps here over many
// RAs
//
$sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission
FROM {$CFG->prefix}role_assignments ra
JOIN {$CFG->prefix}context ctx
ON ra.contextid=ctx.id
LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc
ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid)
WHERE ra.userid = $userid
AND (ctx.path = '$targetpath' OR ctx.path LIKE '{$targetpath}/%')
ORDER BY ctx.depth, ctx.path";
$rs = get_recordset_sql($sql);
// parent paths & roles we need to walk up
// this array will bulk up quite a bit with dups
// which we'll later clear up
$raparents = array();
if ($rs->RecordCount()) {
while ($ra = rs_fetch_next_record($rs)) {
$acc['ra'][$ra->path] = $ra->roleid;
if (!empty($ra->capability)) {
$k = "{$ra->path}:{$ra->roleid}";
$acc['rdef'][$k][$ra->capability] = $ra->permission;
}
$parentids = explode('/', $ra->path);
array_pop($parentids); array_shift($parentids);
if (isset($raparents[$ra->roleid])) {
$raparents[$ra->roleid] = array_merge($raparents[$ra->roleid], $parentids);
} else {
$raparents[$ra->roleid] = $parentids;
}
}
}
rs_close($rs);
// 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()
if (count($raparents)) {
$clauses = array();
foreach ($raparents as $roleid=>$contexts) {
$contexts = sql_intarray_to_in(array_unique($contexts));
if ($contexts ==! '') {
$clauses[] = "(roleid=$roleid AND contextid IN ($contexts))";
}
}
$clauses = join(" OR ", $clauses);
$sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
FROM {$CFG->prefix}role_capabilities rc
JOIN {$CFG->prefix}context ctx
ON rc.contextid=ctx.id
WHERE $clauses
ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
$rs = get_recordset_sql($sql);
if ($rs->RecordCount()) {
while ($rd = rs_fetch_next_record($rs)) {
$k = "{$rd->path}:{$rd->roleid}";
$acc['rdef'][$k][$rd->capability] = $rd->permission;
}
}
rs_close($rs);
}
//
// Overrides for the role assignments IN SUBCONTEXTS
//
// 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 {$CFG->prefix}role_assignments ra
JOIN {$CFG->prefix}context ctx
ON ra.contextid=ctx.id
JOIN {$CFG->prefix}context sctx
ON (sctx.path LIKE ctx.path||'/%')
JOIN {$CFG->prefix}role_capabilities rco
ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
WHERE ra.userid = $userid AND
ORDER BY sctx.depth, sctx.path, ra.roleid";
if ($rs->RecordCount()) {
while ($rd = rs_fetch_next_record($rs)) {
$k = "{$rd->path}:{$rd->roleid}";
$acc['rdef'][$k][$rd->capability] = $rd->permission;
}
}
rs_close($rs);
// TODO: compact capsets?
return $sw;
}
error_log("loaded $targetpath");
$acc['loaded'][] = $targetpath;
return $acc;
}
/**
* A convenience function to completely load all the capabilities
@@ -1436,8 +1648,8 @@ function load_all_capabilities() {
$defcaps = load_defaultuser_role(true);
}
load_user_capability();
$USER->access=get_user_sitewide_access($USER->id);
//load_user_capability();
$USER->access=get_user_access_sitewide($USER->id);
// when in "course login as" - load only course caqpabilitites (it may not always work as expected)
if (!empty($USER->realuser) and $USER->loginascontext->contextlevel != CONTEXT_SYSTEM) {