/** * This function returns whether the current user has the capability of performing a function * For example, we can do has_capability('mod/forum:replypost',$context) in forum * @param string $capability - name of the capability (or debugcache or clearcache) * @param object $context - a context object (record from context table) * @param integer $userid - a userid number, empty if current $USER * @param bool $doanything - if false, ignore do anything * @return bool */ function has_capability($capability, $context, $userid = NULL, $doanything = true) { global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE; if (empty($CFG->rolesactive)) { if ($SCRIPT === "/{$CFG->admin}/index.php" or $SCRIPT === "/{$CFG->admin}/cliupgrade.php") { // we are in an installer - roles can not work yet return true; } else { return false; } } // the original $CONTEXT here was hiding serious errors // for security reasons do not reuse previous context if (empty($context)) { debugging('Incorrect context specified'); return false; } /// Some sanity checks if (debugging('', DEBUG_DEVELOPER)) { if (!is_valid_capability($capability)) { debugging('Capability "' . $capability . '" was not found! This should be fixed in code.'); } if (!is_bool($doanything)) { debugging('Capability parameter "doanything" is wierd ("' . $doanything . '"). This should be fixed in code.'); } } if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid $userid = $USER->id; } 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 } else { load_user_accessdata($userid); $ACCESSLIB_PRIVATE->dirtycontexts = array(); } $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; } else { if ($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(); } } // 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(); } else { reload_all_capabilities(); } } // divulge how many times we are called //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability"); if ($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, $doanything); } // 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, $doanything); } if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { load_user_accessdata($userid); } if ($context->contextlevel <= CONTEXT_COURSE) { // Course and above are always preloaded return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid], $doanything); } // 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]); } return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid], $doanything); }
/** * Check whether a user has a particular capability in a given context. * * For example:: * $context = get_context_instance(CONTEXT_MODULE, $cm->id); * has_capability('mod/forum:replypost',$context) * * By default checks the capabilities of the current user, but you can pass a * different userid. By default will return true for admin users, but you can override that with the fourth argument. * * Guest and not-logged-in users can never get any dangerous capability - that is any write capability * 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 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; if (during_initial_install()) { if ($SCRIPT === "/{$CFG->admin}/index.php" or $SCRIPT === "/{$CFG->admin}/cliupgrade.php") { // we are in an installer - roles can not work yet return true; } else { return false; } } if (strpos($capability, 'moodle/legacy:') === 0) { 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'); return false; } if (!is_bool($doanything)) { throw new coding_exception('Capability parameter "doanything" is wierd ("' . $doanything . '"). This has to be fixed in code.'); } // 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; } // capability must exist if (!($capinfo = get_capability_info($capability))) { debugging('Capability "' . $capability . '" was not found! This should be fixed in code.'); 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 (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 } 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(); } } // 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(); } else { reload_all_capabilities(); } } // Find out if user is admin - it is not possible to override the doanything in any way // and it is not possible to switch to admin role either. if ($doanything) { if (is_siteadmin($userid)) { if ($userid != $USER->id) { return true; } // make sure switchrole is not used in this context if (empty($USER->access['rsw'])) { return true; } $parts = explode('/', trim($context->path, '/')); $path = ''; $switched = false; foreach ($parts as $part) { $path .= '/' . $part; if (!empty($USER->access['rsw'][$path])) { $switched = true; break; } } if (!$switched) { return true; } //ok, admin switched role in this context, let's use normal access control rules } } // divulge how many times we are called //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability"); 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); } // 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); } if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { load_user_accessdata($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]); } return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]); }
if (!empty($CFG->notloggedinroleid)) { $accessdata = get_role_access($CFG->notloggedinroleid); $accessdata['ra'][$systempath] = array($CFG->notloggedinroleid); } else { $accessdata = array(); $accessdata['ra'] = array(); $accessdata['rdef'] = array(); $accessdata['loaded'] = array(); } } else { if (isguestuser($user)) { $guestrole = get_guest_role(); $accessdata = get_role_access($guestrole->id); $accessdata['ra'][$systempath] = array($guestrole->id); } else { load_user_accessdata($userid); $accessdata = $ACCESS[$userid]; } } if ($context->contextlevel > CONTEXT_COURSE && !path_inaccessdata($context->path, $accessdata)) { load_subcontext($userid, $context, $accessdata); } // Load the roles we need. $roleids = array(); foreach ($accessdata['ra'] as $roleassignments) { $roleids = array_merge($roleassignments, $roleids); } $roles = get_records_list('role', 'id', $roleids); $rolenames = array(); foreach ($roles as $role) { $rolenames[$role->id] = $role->name;
function has_capability($capability, $context, $userid = NULL, $doanything = true) { global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS; // the original $CONTEXT here was hiding serious errors // for security reasons do notreuse previous context if (empty($context)) { debugging('Incorrect context specified'); return false; } if (is_null($userid) || $userid === 0) { $userid = $USER->id; } $contexts = array(); $basepath = '/' . SYSCONTEXTID; if (empty($context->path)) { $contexts[] = SYSCONTEXTID; $context->path = $basepath; 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 === 0 && !isset($USER->access)) { // not-logged-in user first time here load_all_capabilities(); } else { if (defined('FULLME') && FULLME === 'cron' && !isset($USER->access)) { // // In cron, some modules setup a 'fake' $USER, // ensure we load the appropriate accessdata. // Also: set $DIRTYCONTEXTS to empty // if (!isset($ACCESS)) { $ACCESS = array(); } if (!isset($ACCESS[$userid])) { load_user_accessdata($userid); } $USER->access = $ACCESS[$userid]; $DIRTYCONTEXTS = array(); } else { if ($USER->id === $userid && !isset($USER->access)) { // caps not loaded yet - better to load them to keep BC with 1.8 // probably $USER object set up manually load_all_capabilities(); } } } // Careful check for staleness... $clean = true; if (!isset($DIRTYCONTEXTS)) { // Load dirty contexts list $DIRTYCONTEXTS = get_dirty_contexts($USER->access['time']); // Check basepath only once, when // we load the dirty contexts... if (isset($DIRTYCONTEXTS[$basepath])) { // sitewide change, dirty $clean = false; } } // Check for staleness in the whole parenthood if ($clean && !is_contextpath_clean($context->path, $DIRTYCONTEXTS)) { $clean = false; } if (!$clean) { // reload all capabilities - preserving loginas, roleswitches, etc // and then cleanup any marks of dirtyness... at least from our short // term memory! :-) reload_all_capabilities(); $DIRTYCONTEXTS = array(); $clean = true; } // divulge how many times we are called //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability"); 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_capability_in_accessdata($capability, $context, $USER->access, $doanything); } // 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']}"); $USER->access = get_user_access_bycontext($USER->id, $context, $USER->access); } return has_capability_in_accessdata($capability, $context, $USER->access, $doanything); } if (!isset($ACCESS)) { $ACCESS = array(); } if (!isset($ACCESS[$userid])) { load_user_accessdata($userid); } if ($context->contextlevel <= CONTEXT_COURSE) { // Course and above are always preloaded return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything); } // Load accessdata for below-the-course contexts as needed if (!path_inaccessdata($context->path, $ACCESS[$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']}"); $ACCESS[$userid] = get_user_access_bycontext($userid, $context, $ACCESS[$userid]); } return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything); }