MDL-23948 making $ACCESSLIB_PRIVATE proper global
[moodle.git] / lib / accesslib.php
CommitLineData
46808d7c 1<?php
2
117bd748
PS
3// This file is part of Moodle - http://moodle.org/
4//
46808d7c 5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
117bd748 14//
46808d7c 15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
6cdd0f9c 17
92e53168 18/**
46808d7c 19 * This file contains functions for managing user access
20 *
cc3edaa4 21 * <b>Public API vs internals</b>
5a4e7398 22 *
92e53168 23 * General users probably only care about
24 *
dcd6a775 25 * Context handling
26 * - get_context_instance()
27 * - get_context_instance_by_id()
28 * - get_parent_contexts()
29 * - get_child_contexts()
5a4e7398 30 *
dcd6a775 31 * Whether the user can do something...
92e53168 32 * - has_capability()
8a1b1c32 33 * - has_any_capability()
34 * - has_all_capabilities()
efd6fce5 35 * - require_capability()
dcd6a775 36 * - require_login() (from moodlelib)
37 *
38 * What courses has this user access to?
92e53168 39 * - get_user_courses_bycap()
dcd6a775 40 *
db70c4bd 41 * What users can do X in this context?
42 * - get_users_by_capability()
43 *
dcd6a775 44 * Enrol/unenrol
ad833c42 45 * - enrol_into_course()
46 * - role_assign()/role_unassign()
5a4e7398 47 *
92e53168 48 *
49 * Advanced use
dcd6a775 50 * - load_all_capabilities()
51 * - reload_all_capabilities()
bb2c22bd 52 * - has_capability_in_accessdata()
dcd6a775 53 * - is_siteadmin()
54 * - get_user_access_sitewide()
a2cf7f1b 55 * - load_subcontext()
dcd6a775 56 * - get_role_access_bycontext()
57 *
cc3edaa4 58 * <b>Name conventions</b>
5a4e7398 59 *
cc3edaa4 60 * "ctx" means context
92e53168 61 *
cc3edaa4 62 * <b>accessdata</b>
92e53168 63 *
64 * Access control data is held in the "accessdata" array
65 * which - for the logged-in user, will be in $USER->access
5a4e7398 66 *
d867e696 67 * For other users can be generated and passed around (but may also be cached
68 * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser.
92e53168 69 *
bb2c22bd 70 * $accessdata is a multidimensional array, holding
5a4e7398 71 * role assignments (RAs), role-capabilities-perm sets
51be70d2 72 * (role defs) and a list of courses we have loaded
92e53168 73 * data for.
74 *
5a4e7398 75 * Things are keyed on "contextpaths" (the path field of
92e53168 76 * the context table) for fast walking up/down the tree.
cc3edaa4 77 * <code>
bb2c22bd 78 * $accessdata[ra][$contextpath]= array($roleid)
79 * [$contextpath]= array($roleid)
5a4e7398 80 * [$contextpath]= array($roleid)
117bd748 81 * </code>
92e53168 82 *
83 * Role definitions are stored like this
84 * (no cap merge is done - so it's compact)
85 *
cc3edaa4 86 * <code>
bb2c22bd 87 * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
88 * [mod/forum:editallpost] = -1
89 * [mod/forum:startdiscussion] = -1000
cc3edaa4 90 * </code>
92e53168 91 *
bb2c22bd 92 * See how has_capability_in_accessdata() walks up/down the tree.
92e53168 93 *
94 * Normally - specially for the logged-in user, we only load
95 * rdef and ra down to the course level, but not below. This
96 * keeps accessdata small and compact. Below-the-course ra/rdef
97 * are loaded as needed. We keep track of which courses we
5a4e7398 98 * have loaded ra/rdef in
cc3edaa4 99 * <code>
5a4e7398 100 * $accessdata[loaded] = array($contextpath, $contextpath)
cc3edaa4 101 * </code>
92e53168 102 *
cc3edaa4 103 * <b>Stale accessdata</b>
92e53168 104 *
105 * For the logged-in user, accessdata is long-lived.
106 *
d867e696 107 * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
92e53168 108 * context paths affected by changes. Any check at-or-below
109 * a dirty context will trigger a transparent reload of accessdata.
5a4e7398 110 *
4f65e0fb 111 * Changes at the system level will force the reload for everyone.
92e53168 112 *
cc3edaa4 113 * <b>Default role caps</b>
5a4e7398 114 * The default role assignment is not in the DB, so we
115 * add it manually to accessdata.
92e53168 116 *
117 * This means that functions that work directly off the
118 * DB need to ensure that the default role caps
5a4e7398 119 * are dealt with appropriately.
92e53168 120 *
78bfb562
PS
121 * @package core
122 * @subpackage role
123 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
124 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92e53168 125 */
bbbf2d40 126
78bfb562
PS
127defined('MOODLE_INTERNAL') || die();
128
cc3edaa4 129/** permission definitions */
e49e61bf 130define('CAP_INHERIT', 0);
cc3edaa4 131/** permission definitions */
bbbf2d40 132define('CAP_ALLOW', 1);
cc3edaa4 133/** permission definitions */
bbbf2d40 134define('CAP_PREVENT', -1);
cc3edaa4 135/** permission definitions */
bbbf2d40 136define('CAP_PROHIBIT', -1000);
137
cc3edaa4 138/** context definitions */
bbbf2d40 139define('CONTEXT_SYSTEM', 10);
cc3edaa4 140/** context definitions */
4b10f08b 141define('CONTEXT_USER', 30);
cc3edaa4 142/** context definitions */
bbbf2d40 143define('CONTEXT_COURSECAT', 40);
cc3edaa4 144/** context definitions */
bbbf2d40 145define('CONTEXT_COURSE', 50);
cc3edaa4 146/** context definitions */
bbbf2d40 147define('CONTEXT_MODULE', 70);
cc3edaa4 148/** context definitions */
bbbf2d40 149define('CONTEXT_BLOCK', 80);
150
cc3edaa4 151/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
21b6db6e 152define('RISK_MANAGETRUST', 0x0001);
cc3edaa4 153/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
a6b02b65 154define('RISK_CONFIG', 0x0002);
cc3edaa4 155/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
21b6db6e 156define('RISK_XSS', 0x0004);
cc3edaa4 157/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
21b6db6e 158define('RISK_PERSONAL', 0x0008);
cc3edaa4 159/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
21b6db6e 160define('RISK_SPAM', 0x0010);
cc3edaa4 161/** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
3a0c6cca 162define('RISK_DATALOSS', 0x0020);
21b6db6e 163
cc3edaa4 164/** rolename displays - the name as defined in the role definition */
165define('ROLENAME_ORIGINAL', 0);
166/** rolename displays - the name as defined by a role alias */
167define('ROLENAME_ALIAS', 1);
168/** rolename displays - Both, like this: Role alias (Original)*/
169define('ROLENAME_BOTH', 2);
170/** rolename displays - the name as defined in the role definition and the shortname in brackets*/
171define('ROLENAME_ORIGINALANDSHORT', 3);
172/** rolename displays - the name as defined by a role alias, in raw form suitable for editing*/
173define('ROLENAME_ALIAS_RAW', 4);
df997f84
PS
174/** rolename displays - the name is simply short role name*/
175define('ROLENAME_SHORT', 5);
cc3edaa4 176
177/** size limit for context cache */
117bd748 178if (!defined('MAX_CONTEXT_CACHE_SIZE')) {
4d10247f 179 define('MAX_CONTEXT_CACHE_SIZE', 5000);
180}
181
cc3edaa4 182/**
117bd748 183 * Although this looks like a global variable, it isn't really.
cc3edaa4 184 *
117bd748
PS
185 * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
186 * It is used to cache various bits of data between function calls for performance reasons.
4f65e0fb 187 * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
cc3edaa4 188 * as methods of a class, instead of functions.
189 *
190 * @global stdClass $ACCESSLIB_PRIVATE
191 * @name $ACCESSLIB_PRIVATE
192 */
f14438dc 193global $ACCESSLIB_PRIVATE;
d867e696 194$ACCESSLIB_PRIVATE = new stdClass;
195$ACCESSLIB_PRIVATE->contexts = array(); // Cache of context objects by level and instance
196$ACCESSLIB_PRIVATE->contextsbyid = array(); // Cache of context objects by id
4f0c2d00
PS
197$ACCESSLIB_PRIVATE->systemcontext = NULL; // Used in get_system_context
198$ACCESSLIB_PRIVATE->dirtycontexts = NULL; // Dirty contexts cache
d867e696 199$ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER
200$ACCESSLIB_PRIVATE->roledefinitions = array(); // role definitions cache - helps a lot with mem usage in cron
201$ACCESSLIB_PRIVATE->croncache = array(); // Used in get_role_access
202$ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts.
4f0c2d00 203$ACCESSLIB_PRIVATE->capabilities = NULL; // detailed information about the capabilities
bbbf2d40 204
d867e696 205/**
46808d7c 206 * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
117bd748 207 *
d867e696 208 * This method should ONLY BE USED BY UNIT TESTS. It clears all of
209 * accesslib's private caches. You need to do this before setting up test data,
4f65e0fb 210 * and also at the end of the tests.
cc3edaa4 211 * @global object
212 * @global object
213 * @global object
d867e696 214 */
215function accesslib_clear_all_caches_for_unit_testing() {
216 global $UNITTEST, $USER, $ACCESSLIB_PRIVATE;
217 if (empty($UNITTEST->running)) {
218 throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
219 }
220 $ACCESSLIB_PRIVATE->contexts = array();
221 $ACCESSLIB_PRIVATE->contextsbyid = array();
4f0c2d00
PS
222 $ACCESSLIB_PRIVATE->systemcontext = NULL;
223 $ACCESSLIB_PRIVATE->dirtycontexts = NULL;
d867e696 224 $ACCESSLIB_PRIVATE->accessdatabyuser = array();
225 $ACCESSLIB_PRIVATE->roledefinitions = array();
226 $ACCESSLIB_PRIVATE->croncache = array();
227 $ACCESSLIB_PRIVATE->preloadedcourses = array();
4f0c2d00 228 $ACCESSLIB_PRIVATE->capabilities = NULL;
d867e696 229
230 unset($USER->access);
231}
232
233/**
234 * Private function. Add a context object to accesslib's caches.
cc3edaa4 235 * @global object
46808d7c 236 * @param object $context
d867e696 237 */
238function cache_context($context) {
239 global $ACCESSLIB_PRIVATE;
4d10247f 240
241 // If there are too many items in the cache already, remove items until
242 // there is space
243 while (count($ACCESSLIB_PRIVATE->contextsbyid) >= MAX_CONTEXT_CACHE_SIZE) {
56f3599b 244 $first = reset($ACCESSLIB_PRIVATE->contextsbyid);
245 unset($ACCESSLIB_PRIVATE->contextsbyid[$first->id]);
4d10247f 246 unset($ACCESSLIB_PRIVATE->contexts[$first->contextlevel][$first->instanceid]);
247 }
248
d867e696 249 $ACCESSLIB_PRIVATE->contexts[$context->contextlevel][$context->instanceid] = $context;
250 $ACCESSLIB_PRIVATE->contextsbyid[$context->id] = $context;
251}
7700027f 252
46808d7c 253/**
254 * This is really slow!!! do not use above course context level
255 *
cc3edaa4 256 * @global object
46808d7c 257 * @param int $roleid
258 * @param object $context
259 * @return array
260 */
eef879ec 261function get_role_context_caps($roleid, $context) {
f33e1ed4 262 global $DB;
263
eef879ec 264 //this is really slow!!!! - do not use above course context level!
265 $result = array();
266 $result[$context->id] = array();
e7876c1e 267
eef879ec 268 // first emulate the parent context capabilities merging into context
269 $searchcontexts = array_reverse(get_parent_contexts($context));
270 array_push($searchcontexts, $context->id);
271 foreach ($searchcontexts as $cid) {
f33e1ed4 272 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
eef879ec 273 foreach ($capabilities as $cap) {
274 if (!array_key_exists($cap->capability, $result[$context->id])) {
275 $result[$context->id][$cap->capability] = 0;
276 }
277 $result[$context->id][$cap->capability] += $cap->permission;
278 }
279 }
280 }
e7876c1e 281
eef879ec 282 // now go through the contexts bellow given context
19bb8a05 283 $searchcontexts = array_keys(get_child_contexts($context));
eef879ec 284 foreach ($searchcontexts as $cid) {
f33e1ed4 285 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
eef879ec 286 foreach ($capabilities as $cap) {
287 if (!array_key_exists($cap->contextid, $result)) {
288 $result[$cap->contextid] = array();
289 }
290 $result[$cap->contextid][$cap->capability] = $cap->permission;
291 }
292 }
e7876c1e 293 }
294
eef879ec 295 return $result;
296}
297
eef879ec 298/**
46808d7c 299 * Gets the accessdata for role "sitewide" (system down to course)
343effbe 300 *
cc3edaa4 301 * @global object
302 * @global object
46808d7c 303 * @param int $roleid
4f0c2d00 304 * @param array $accessdata defaults to NULL
e0376a62 305 * @return array
eef879ec 306 */
bb2c22bd 307function get_role_access($roleid, $accessdata=NULL) {
cdfa3035 308
f33e1ed4 309 global $CFG, $DB;
eef879ec 310
e0376a62 311 /* Get it in 1 cheap DB query...
312 * - relevant role caps at the root and down
313 * to the course level - but not below
314 */
bb2c22bd 315 if (is_null($accessdata)) {
316 $accessdata = array(); // named list
317 $accessdata['ra'] = array();
318 $accessdata['rdef'] = array();
319 $accessdata['loaded'] = array();
e7876c1e 320 }
321
e0376a62 322 //
323 // Overrides for the role IN ANY CONTEXTS
324 // down to COURSE - not below -
325 //
326 $sql = "SELECT ctx.path,
327 rc.capability, rc.permission
f33e1ed4 328 FROM {context} ctx
329 JOIN {role_capabilities} rc
330 ON rc.contextid=ctx.id
331 WHERE rc.roleid = ?
332 AND ctx.contextlevel <= ".CONTEXT_COURSE."
333 ORDER BY ctx.depth, ctx.path";
334 $params = array($roleid);
133d5a97 335
a91b910e 336 // we need extra caching in CLI scripts and cron
337 if (CLI_SCRIPT) {
d867e696 338 global $ACCESSLIB_PRIVATE;
133d5a97 339
d867e696 340 if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) {
341 $ACCESSLIB_PRIVATE->croncache[$roleid] = array();
f33e1ed4 342 if ($rs = $DB->get_recordset_sql($sql, $params)) {
343 foreach ($rs as $rd) {
d867e696 344 $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd;
133d5a97 345 }
f33e1ed4 346 $rs->close();
133d5a97 347 }
348 }
349
d867e696 350 foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) {
03cedd62 351 $k = "{$rd->path}:{$roleid}";
352 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
e0376a62 353 }
5a4e7398 354
133d5a97 355 } else {
f33e1ed4 356 if ($rs = $DB->get_recordset_sql($sql, $params)) {
357 foreach ($rs as $rd) {
133d5a97 358 $k = "{$rd->path}:{$roleid}";
359 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
360 }
361 unset($rd);
f33e1ed4 362 $rs->close();
133d5a97 363 }
8d2b18a8 364 }
e0376a62 365
bb2c22bd 366 return $accessdata;
e7876c1e 367}
368
4e1fe7d1 369/**
46808d7c 370 * Gets the accessdata for role "sitewide" (system down to course)
4e1fe7d1 371 *
cc3edaa4 372 * @global object
373 * @global object
46808d7c 374 * @param int $roleid
4f0c2d00 375 * @param array $accessdata defaults to NULL
4e1fe7d1 376 * @return array
377 */
378function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
379
f33e1ed4 380 global $CFG, $DB;
5a4e7398 381
4e1fe7d1 382 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
383 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
5a4e7398 384
4e1fe7d1 385 //
386 // Overrides for the role in any contexts related to the course
387 //
388 $sql = "SELECT ctx.path,
389 rc.capability, rc.permission
f33e1ed4 390 FROM {context} ctx
391 JOIN {role_capabilities} rc
392 ON rc.contextid=ctx.id
393 WHERE rc.roleid = ?
394 AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?)
395 AND ctx.contextlevel <= ".CONTEXT_COURSE."
396 ORDER BY ctx.depth, ctx.path";
397 $params = array($roleid, "$base/%");
5a4e7398 398
f33e1ed4 399 if ($rs = $DB->get_recordset_sql($sql, $params)) {
400 foreach ($rs as $rd) {
03cedd62 401 $k = "{$rd->path}:{$roleid}";
402 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
4e1fe7d1 403 }
03cedd62 404 unset($rd);
f33e1ed4 405 $rs->close();
4e1fe7d1 406 }
407
408 return $accessdata;
409}
410
411
8f8ed475 412/**
413 * Get the default guest role
117bd748 414 *
cc3edaa4 415 * @global object
416 * @global object
8f8ed475 417 * @return object role
418 */
419function get_guest_role() {
f33e1ed4 420 global $CFG, $DB;
ebce32b5 421
422 if (empty($CFG->guestroleid)) {
4f0c2d00 423 if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
ebce32b5 424 $guestrole = array_shift($roles); // Pick the first one
425 set_config('guestroleid', $guestrole->id);
426 return $guestrole;
427 } else {
428 debugging('Can not find any guest role!');
429 return false;
430 }
8f8ed475 431 } else {
f33e1ed4 432 if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
ebce32b5 433 return $guestrole;
434 } else {
435 //somebody is messing with guest roles, remove incorrect setting and try to find a new one
436 set_config('guestroleid', '');
437 return get_guest_role();
438 }
8f8ed475 439 }
440}
441
128f0984 442/**
4f65e0fb 443 * Check whether a user has a particular capability in a given context.
46808d7c 444 *
41e87d30 445 * For example::
446 * $context = get_context_instance(CONTEXT_MODULE, $cm->id);
447 * has_capability('mod/forum:replypost',$context)
46808d7c 448 *
4f65e0fb 449 * By default checks the capabilities of the current user, but you can pass a
4f0c2d00
PS
450 * different userid. By default will return true for admin users, but you can override that with the fourth argument.
451 *
452 * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
453 * or capabilities with XSS, config or data loss risks.
117bd748 454 *
41e87d30 455 * @param string $capability the name of the capability to check. For example mod/forum:view
456 * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
4f0c2d00
PS
457 * @param integer|object $user A user id or object. By default (NULL) checks the permissions of the current user.
458 * @param boolean $doanything If false, ignores effect of admin role assignment
41e87d30 459 * @return boolean true if the user has this capability. Otherwise false.
128f0984 460 */
4f0c2d00 461function has_capability($capability, $context, $user = NULL, $doanything=true) {
d867e696 462 global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
18818abf 463
31a99877 464 if (during_initial_install()) {
18818abf 465 if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") {
466 // we are in an installer - roles can not work yet
467 return true;
468 } else {
469 return false;
470 }
471 }
7f97ea29 472
4f0c2d00
PS
473 if (strpos($capability, 'moodle/legacy:') === 0) {
474 throw new coding_exception('Legacy capabilities can not be used any more!');
475 }
476
7d0c81b3 477 // the original $CONTEXT here was hiding serious errors
128f0984 478 // for security reasons do not reuse previous context
7d0c81b3 479 if (empty($context)) {
480 debugging('Incorrect context specified');
481 return false;
74ac5b66 482 }
4f0c2d00
PS
483 if (!is_bool($doanything)) {
484 throw new coding_exception('Capability parameter "doanything" is wierd ("'.$doanything.'"). This has to be fixed in code.');
485 }
7f97ea29 486
4f0c2d00
PS
487 // make sure there is a real user specified
488 if ($user === NULL) {
489 $userid = !empty($USER->id) ? $USER->id : 0;
490 } else {
491 $userid = !empty($user->id) ? $user->id : $user;
cc3d5e10 492 }
493
4f0c2d00
PS
494 // capability must exist
495 if (!$capinfo = get_capability_info($capability)) {
496 debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
497 return false;
498 }
499 // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
500 if (($capinfo->captype === 'write') or ((int)$capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
501 if (isguestuser($userid) or $userid == 0) {
502 return false;
c84a2dbe 503 }
7f97ea29 504 }
505
128f0984 506 if (is_null($context->path) or $context->depth == 0) {
507 //this should not happen
508 $contexts = array(SYSCONTEXTID, $context->id);
509 $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
510 debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
511
7f97ea29 512 } else {
513 $contexts = explode('/', $context->path);
514 array_shift($contexts);
515 }
516
a91b910e 517 if (CLI_SCRIPT && !isset($USER->access)) {
1a9b6787 518 // In cron, some modules setup a 'fake' $USER,
519 // ensure we load the appropriate accessdata.
d867e696 520 if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
521 $ACCESSLIB_PRIVATE->dirtycontexts = NULL; //load fresh dirty contexts
128f0984 522 } else {
1a9b6787 523 load_user_accessdata($userid);
d867e696 524 $ACCESSLIB_PRIVATE->dirtycontexts = array();
1a9b6787 525 }
d867e696 526 $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
7d0c81b3 527
7fde45a7 528 } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) {
7d0c81b3 529 // caps not loaded yet - better to load them to keep BC with 1.8
128f0984 530 // not-logged-in user or $USER object set up manually first time here
7d0c81b3 531 load_all_capabilities();
d867e696 532 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now
533 $ACCESSLIB_PRIVATE->roledefinitions = array();
1a9b6787 534 }
535
128f0984 536 // Load dirty contexts list if needed
d867e696 537 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
7be3be1b 538 if (isset($USER->access['time'])) {
d867e696 539 $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
7be3be1b 540 }
541 else {
d867e696 542 $ACCESSLIB_PRIVATE->dirtycontexts = array();
7be3be1b 543 }
148eb2a7 544 }
128f0984 545
546 // Careful check for staleness...
d867e696 547 if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
ef989bd9 548 // reload all capabilities - preserving loginas, roleswitches, etc
549 // and then cleanup any marks of dirtyness... at least from our short
550 // term memory! :-)
d867e696 551 $ACCESSLIB_PRIVATE->accessdatabyuser = array();
552 $ACCESSLIB_PRIVATE->roledefinitions = array();
128f0984 553
a91b910e 554 if (CLI_SCRIPT) {
128f0984 555 load_user_accessdata($userid);
d867e696 556 $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
557 $ACCESSLIB_PRIVATE->dirtycontexts = array();
128f0984 558
559 } else {
560 reload_all_capabilities();
561 }
148eb2a7 562 }
128f0984 563
4f0c2d00
PS
564 // Find out if user is admin - it is not possible to override the doanything in any way
565 // and it is not possible to switch to admin role either.
566 if ($doanything) {
567 if (is_siteadmin($userid)) {
7abbc5c2
PS
568 if ($userid != $USER->id) {
569 return true;
570 }
571 // make sure switchrole is not used in this context
572 if (empty($USER->access['rsw'])) {
573 return true;
574 }
575 $parts = explode('/', trim($context->path, '/'));
576 $path = '';
577 $switched = false;
578 foreach ($parts as $part) {
579 $path .= '/' . $part;
580 if (!empty($USER->access['rsw'][$path])) {
581 $switched = true;
582 break;
583 }
584 }
585 if (!$switched) {
586 return true;
587 }
588 //ok, admin switched role in this context, let's use normal access control rules
4f0c2d00
PS
589 }
590 }
591
13a79475 592 // divulge how many times we are called
593 //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
594
7fde45a7 595 if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid
74ac5b66 596 //
597 // For the logged in user, we have $USER->access
598 // which will have all RAs and caps preloaded for
599 // course and above contexts.
600 //
601 // Contexts below courses && contexts that do not
602 // hang from courses are loaded into $USER->access
603 // on demand, and listed in $USER->access[loaded]
604 //
7f97ea29 605 if ($context->contextlevel <= CONTEXT_COURSE) {
606 // Course and above are always preloaded
4f0c2d00 607 return has_capability_in_accessdata($capability, $context, $USER->access);
7f97ea29 608 }
420bfab1 609 // Load accessdata for below-the-course contexts
31c2de82 610 if (!path_inaccessdata($context->path,$USER->access)) {
6100dad0 611 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
74ac5b66 612 // $bt = debug_backtrace();
613 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
a2cf7f1b 614 load_subcontext($USER->id, $context, $USER->access);
74ac5b66 615 }
4f0c2d00 616 return has_capability_in_accessdata($capability, $context, $USER->access);
7f97ea29 617 }
bb2c22bd 618
d867e696 619 if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
204a369c 620 load_user_accessdata($userid);
621 }
4f0c2d00 622
420bfab1 623 if ($context->contextlevel <= CONTEXT_COURSE) {
624 // Course and above are always preloaded
4f0c2d00 625 return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
420bfab1 626 }
627 // Load accessdata for below-the-course contexts as needed
d867e696 628 if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
6100dad0 629 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
420bfab1 630 // $bt = debug_backtrace();
631 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
d867e696 632 load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
420bfab1 633 }
4f0c2d00 634 return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
7f97ea29 635}
636
3fc3ebf2 637/**
41e87d30 638 * Check if the user has any one of several capabilities from a list.
46808d7c 639 *
41e87d30 640 * This is just a utility method that calls has_capability in a loop. Try to put
641 * the capabilities that most users are likely to have first in the list for best
642 * performance.
3fc3ebf2 643 *
644 * There are probably tricks that could be done to improve the performance here, for example,
645 * check the capabilities that are already cached first.
646 *
46808d7c 647 * @see has_capability()
41e87d30 648 * @param array $capabilities an array of capability names.
649 * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
4f0c2d00
PS
650 * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
651 * @param boolean $doanything If false, ignore effect of admin role assignment
41e87d30 652 * @return boolean true if the user has any of these capabilities. Otherwise false.
3fc3ebf2 653 */
654function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
41e87d30 655 if (!is_array($capabilities)) {
656 debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array');
657 return false;
658 }
3fc3ebf2 659 foreach ($capabilities as $capability) {
59664877 660 if (has_capability($capability, $context, $userid, $doanything)) {
3fc3ebf2 661 return true;
662 }
663 }
664 return false;
665}
666
8a1b1c32 667/**
41e87d30 668 * Check if the user has all the capabilities in a list.
46808d7c 669 *
41e87d30 670 * This is just a utility method that calls has_capability in a loop. Try to put
671 * the capabilities that fewest users are likely to have first in the list for best
672 * performance.
8a1b1c32 673 *
674 * There are probably tricks that could be done to improve the performance here, for example,
675 * check the capabilities that are already cached first.
676 *
46808d7c 677 * @see has_capability()
41e87d30 678 * @param array $capabilities an array of capability names.
679 * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
4f0c2d00
PS
680 * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
681 * @param boolean $doanything If false, ignore effect of admin role assignment
41e87d30 682 * @return boolean true if the user has all of these capabilities. Otherwise false.
8a1b1c32 683 */
684function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) {
3ce50127 685 if (!is_array($capabilities)) {
686 debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
687 return false;
688 }
8a1b1c32 689 foreach ($capabilities as $capability) {
690 if (!has_capability($capability, $context, $userid, $doanything)) {
691 return false;
692 }
693 }
694 return true;
695}
696
128f0984 697/**
4f0c2d00 698 * Check if the user is an admin at the site level.
46808d7c 699 *
4f0c2d00
PS
700 * Please note that use of proper capabilities is always encouraged,
701 * this function is supposed to be used from core or for temporary hacks.
39407442 702 *
4f0c2d00
PS
703 * @param int|object $user_or_id user id or user object
704 * @returns bool true if user is one of the administrators, false otherwise
39407442 705 */
4f0c2d00
PS
706function is_siteadmin($user_or_id = NULL) {
707 global $CFG, $USER;
39407442 708
4f0c2d00
PS
709 if ($user_or_id === NULL) {
710 $user_or_id = $USER;
711 }
6cab02ac 712
4f0c2d00
PS
713 if (empty($user_or_id)) {
714 return false;
715 }
716 if (!empty($user_or_id->id)) {
717 // we support
718 $userid = $user_or_id->id;
719 } else {
720 $userid = $user_or_id;
721 }
6cab02ac 722
4f0c2d00
PS
723 $siteadmins = explode(',', $CFG->siteadmins);
724 return in_array($userid, $siteadmins);
6cab02ac 725}
726
bbdb7070 727/**
4f0c2d00 728 * Returns true if user has at least one role assign
df997f84 729 * of 'coursecontact' role (is potentially listed in some course descriptions).
4f0c2d00
PS
730 * @param $userid
731 * @return unknown_type
bbdb7070 732 */
df997f84 733function has_coursecontact_role($userid) {
bbdb7070 734 global $DB;
735
df997f84 736 if (empty($CFG->coursecontact)) {
4f0c2d00
PS
737 return false;
738 }
739 $sql = "SELECT 1
740 FROM {role_assignments}
df997f84 741 WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
4f0c2d00 742 return $DB->record_exists($sql, array('userid'=>$userid));
bbdb7070 743}
744
46808d7c 745/**
746 * @param string $path
747 * @return string
748 */
4f0c2d00 749function get_course_from_path($path) {
7f97ea29 750 // assume that nothing is more than 1 course deep
751 if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
752 return $matches[1];
753 }
754 return false;
755}
756
46808d7c 757/**
758 * @param string $path
759 * @param array $accessdata
760 * @return bool
761 */
bb2c22bd 762function path_inaccessdata($path, $accessdata) {
4f0c2d00
PS
763 if (empty($accessdata['loaded'])) {
764 return false;
765 }
74ac5b66 766
767 // assume that contexts hang from sys or from a course
768 // this will only work well with stuff that hangs from a course
bb2c22bd 769 if (in_array($path, $accessdata['loaded'], true)) {
b738808b 770 // error_log("found it!");
74ac5b66 771 return true;
772 }
773 $base = '/' . SYSCONTEXTID;
774 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
775 $path = $matches[1];
776 if ($path === $base) {
777 return false;
778 }
bb2c22bd 779 if (in_array($path, $accessdata['loaded'], true)) {
74ac5b66 780 return true;
781 }
782 }
783 return false;
784}
785
128f0984 786/**
01a2ce80 787 * Does the user have a capability to do something?
46808d7c 788 *
31c2de82 789 * Walk the accessdata array and return true/false.
6a8d9a38 790 * Deals with prohibits, roleswitching, aggregating
791 * capabilities, etc.
792 *
793 * The main feature of here is being FAST and with no
5a4e7398 794 * side effects.
6a8d9a38 795 *
3ac81bd1 796 * Notes:
797 *
798 * Switch Roles exits early
01a2ce80 799 * ------------------------
3ac81bd1 800 * cap checks within a switchrole need to exit early
801 * in our bottom up processing so they don't "see" that
802 * there are real RAs that can do all sorts of things.
803 *
3d034f77 804 * Switch Role merges with default role
805 * ------------------------------------
806 * If you are a teacher in course X, you have at least
807 * teacher-in-X + defaultloggedinuser-sitewide. So in the
808 * course you'll have techer+defaultloggedinuser.
809 * We try to mimic that in switchrole.
810 *
01a2ce80
PS
811 * Permission evaluation
812 * ---------------------
4f65e0fb 813 * Originally there was an extremely complicated way
01a2ce80 814 * to determine the user access that dealt with
4f65e0fb
PS
815 * "locality" or role assignments and role overrides.
816 * Now we simply evaluate access for each role separately
01a2ce80
PS
817 * and then verify if user has at least one role with allow
818 * and at the same time no role with prohibit.
819 *
46808d7c 820 * @param string $capability
821 * @param object $context
822 * @param array $accessdata
46808d7c 823 * @return bool
6a8d9a38 824 */
4f0c2d00 825function has_capability_in_accessdata($capability, $context, array $accessdata) {
3ac81bd1 826 global $CFG;
827
01a2ce80
PS
828 if (empty($context->id)) {
829 throw new coding_exception('Invalid context specified');
7f97ea29 830 }
3ac81bd1 831
01a2ce80
PS
832 // Build $paths as a list of current + all parent "paths" with order bottom-to-top
833 $contextids = explode('/', trim($context->path, '/'));
834 $paths = array($context->path);
835 while ($contextids) {
836 array_pop($contextids);
837 $paths[] = '/' . implode('/', $contextids);
3ac81bd1 838 }
01a2ce80 839 unset($contextids);
3ac81bd1 840
01a2ce80
PS
841 $roles = array();
842 $switchedrole = false;
6a8d9a38 843
01a2ce80
PS
844 // Find out if role switched
845 if (!empty($accessdata['rsw'])) {
6a8d9a38 846 // From the bottom up...
01a2ce80 847 foreach ($paths as $path) {
1209cb5c 848 if (isset($accessdata['rsw'][$path])) {
01a2ce80 849 // Found a switchrole assignment - check for that role _plus_ the default user role
4f0c2d00 850 $roles = array($accessdata['rsw'][$path]=>NULL, $CFG->defaultuserroleid=>NULL);
01a2ce80
PS
851 $switchedrole = true;
852 break;
6a8d9a38 853 }
854 }
855 }
856
01a2ce80
PS
857 if (!$switchedrole) {
858 // get all users roles in this context and above
859 foreach ($paths as $path) {
860 if (isset($accessdata['ra'][$path])) {
861 foreach ($accessdata['ra'][$path] as $roleid) {
4f0c2d00 862 $roles[$roleid] = NULL;
7f97ea29 863 }
01a2ce80
PS
864 }
865 }
7f97ea29 866 }
867
01a2ce80
PS
868 // Now find out what access is given to each role, going bottom-->up direction
869 foreach ($roles as $roleid => $ignored) {
870 foreach ($paths as $path) {
871 if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
872 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
873 if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) {
874 $roles[$roleid] = $perm;
875 }
876 }
7f97ea29 877 }
01a2ce80
PS
878 }
879 // any CAP_PROHIBIT found means no permission for the user
880 if (array_search(CAP_PROHIBIT, $roles) !== false) {
881 return false;
7f97ea29 882 }
883
01a2ce80
PS
884 // at least one CAP_ALLOW means the user has a permission
885 return (array_search(CAP_ALLOW, $roles) !== false);
7f97ea29 886}
018d4b52 887
46808d7c 888/**
889 * @param object $context
890 * @param array $accessdata
891 * @return array
892 */
bb2c22bd 893function aggregate_roles_from_accessdata($context, $accessdata) {
018d4b52 894
895 $path = $context->path;
896
897 // build $contexts as a list of "paths" of the current
898 // contexts and parents with the order top-to-bottom
899 $contexts = array($path);
900 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
901 $path = $matches[1];
902 array_unshift($contexts, $path);
903 }
018d4b52 904
905 $cc = count($contexts);
906
907 $roles = array();
908 // From the bottom up...
909 for ($n=$cc-1;$n>=0;$n--) {
910 $ctxp = $contexts[$n];
bb2c22bd 911 if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
6cc59cb2 912 // Found assignments on this leaf
bb2c22bd 913 $addroles = $accessdata['ra'][$ctxp];
6cc59cb2 914 $roles = array_merge($roles, $addroles);
018d4b52 915 }
916 }
917
918 return array_unique($roles);
919}
920
0468976c 921/**
41e87d30 922 * A convenience function that tests has_capability, and displays an error if
923 * the user does not have that capability.
8a9c1c1c 924 *
41e87d30 925 * NOTE before Moodle 2.0, this function attempted to make an appropriate
926 * require_login call before checking the capability. This is no longer the case.
927 * You must call require_login (or one of its variants) if you want to check the
928 * user is logged in, before you call this function.
efd6fce5 929 *
46808d7c 930 * @see has_capability()
931 *
41e87d30 932 * @param string $capability the name of the capability to check. For example mod/forum:view
933 * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
4f0c2d00
PS
934 * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
935 * @param bool $doanything If false, ignore effect of admin role assignment
41e87d30 936 * @param string $errorstring The error string to to user. Defaults to 'nopermissions'.
937 * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
938 * @return void terminates with an error if the user does not have the given capability.
0468976c 939 */
41e87d30 940function require_capability($capability, $context, $userid = NULL, $doanything = true,
941 $errormessage = 'nopermissions', $stringfile = '') {
d74067e8 942 if (!has_capability($capability, $context, $userid, $doanything)) {
9a0df45a 943 throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
0468976c 944 }
945}
946
128f0984 947/**
46808d7c 948 * Get an array of courses where cap requested is available
df997f84 949 * and user is enrolled, this can be relatively slow.
46808d7c 950 *
e1d5e5c1 951 * @param string $capability - name of the capability
df997f84
PS
952 * @param array $accessdata_ignored
953 * @param bool $doanything_ignored
e1d5e5c1 954 * @param string $sort - sorting fields - prefix each fieldname with "c."
955 * @param array $fields - additional fields you are interested in...
df997f84 956 * @param int $limit_ignored
e1d5e5c1 957 * @return array $courses - ordered array of course objects - see notes above
e1d5e5c1 958 */
df997f84 959function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort='c.sortorder ASC', $fields=NULL, $limit_ignored=0) {
e1d5e5c1 960
df997f84 961 //TODO: this should be most probably deprecated
e1d5e5c1 962
df997f84
PS
963 $courses = enrol_get_users_courses($userid, true, $fields, $sort);
964 foreach ($courses as $id=>$course) {
965 $context = get_context_instance(CONTEXT_COURSE, $id);
966 if (!has_capability($cap, $context, $userid)) {
967 unset($courses[$id]);
573674bf 968 }
e1d5e5c1 969 }
5a4e7398 970
e1d5e5c1 971 return $courses;
972}
973
b5a645b4 974
a9bee37e 975/**
46808d7c 976 * Return a nested array showing role assignments
a9bee37e 977 * all relevant role capabilities for the user at
df997f84 978 * site/course_category/course levels
a9bee37e 979 *
980 * We do _not_ delve deeper than courses because the number of
981 * overrides at the module/block levels is HUGE.
982 *
f5930992 983 * [ra] => [/path/][]=roleid
a9bee37e 984 * [rdef] => [/path/:roleid][capability]=permission
74ac5b66 985 * [loaded] => array('/path', '/path')
a9bee37e 986 *
cc3edaa4 987 * @global object
988 * @global object
a9bee37e 989 * @param $userid integer - the id of the user
a9bee37e 990 */
74ac5b66 991function get_user_access_sitewide($userid) {
a9bee37e 992
f33e1ed4 993 global $CFG, $DB;
a9bee37e 994
a9bee37e 995 /* Get in 3 cheap DB queries...
f5930992 996 * - role assignments
a9bee37e 997 * - relevant role caps
f5930992 998 * - above and within this user's RAs
a9bee37e 999 * - below this user's RAs - limited to course level
1000 */
1001
bb2c22bd 1002 $accessdata = array(); // named list
1003 $accessdata['ra'] = array();
1004 $accessdata['rdef'] = array();
1005 $accessdata['loaded'] = array();
a9bee37e 1006
a9bee37e 1007 //
f5930992 1008 // Role assignments
a9bee37e 1009 //
f5930992 1010 $sql = "SELECT ctx.path, ra.roleid
f33e1ed4 1011 FROM {role_assignments} ra
f5930992 1012 JOIN {context} ctx ON ctx.id=ra.contextid
1013 WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE;
f33e1ed4 1014 $params = array($userid);
1015 $rs = $DB->get_recordset_sql($sql, $params);
f5930992 1016
018d4b52 1017 //
1018 // raparents collects paths & roles we need to walk up
1019 // the parenthood to build the rdef
1020 //
a9bee37e 1021 $raparents = array();
128f0984 1022 if ($rs) {
f33e1ed4 1023 foreach ($rs as $ra) {
03cedd62 1024 // RAs leafs are arrays to support multi
1025 // role assignments...
1026 if (!isset($accessdata['ra'][$ra->path])) {
1027 $accessdata['ra'][$ra->path] = array();
1028 }
f5930992 1029 array_push($accessdata['ra'][$ra->path], $ra->roleid);
1030
1031 // Concatenate as string the whole path (all related context)
1032 // for this role. This is damn faster than using array_merge()
1033 // Will unique them later
1034 if (isset($raparents[$ra->roleid])) {
1035 $raparents[$ra->roleid] .= $ra->path;
1036 } else {
1037 $raparents[$ra->roleid] = $ra->path;
03cedd62 1038 }
a9bee37e 1039 }
03cedd62 1040 unset($ra);
f33e1ed4 1041 $rs->close();
a9bee37e 1042 }
a9bee37e 1043
1044 // Walk up the tree to grab all the roledefs
1045 // of interest to our user...
f5930992 1046 //
a9bee37e 1047 // NOTE: we use a series of IN clauses here - which
1048 // might explode on huge sites with very convoluted nesting of
1049 // categories... - extremely unlikely that the number of categories
1050 // and roletypes is so large that we hit the limits of IN()
f5930992 1051 $clauses = '';
f33e1ed4 1052 $cparams = array();
f5930992 1053 foreach ($raparents as $roleid=>$strcontexts) {
1054 $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/'))));
a9bee37e 1055 if ($contexts ==! '') {
f5930992 1056 if ($clauses) {
1057 $clauses .= ' OR ';
1058 }
1059 $clauses .= "(roleid=? AND contextid IN ($contexts))";
f33e1ed4 1060 $cparams[] = $roleid;
a9bee37e 1061 }
1062 }
f5930992 1063
d4c4ecb8 1064 if ($clauses !== '') {
1065 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
f33e1ed4 1066 FROM {role_capabilities} rc
1067 JOIN {context} ctx
d4c4ecb8 1068 ON rc.contextid=ctx.id
f5930992 1069 WHERE $clauses";
1070
d4c4ecb8 1071 unset($clauses);
f5930992 1072 $rs = $DB->get_recordset_sql($sql, $cparams);
a9bee37e 1073
0dbb2191 1074 if ($rs) {
f33e1ed4 1075 foreach ($rs as $rd) {
0dbb2191 1076 $k = "{$rd->path}:{$rd->roleid}";
1077 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1078 }
1079 unset($rd);
f33e1ed4 1080 $rs->close();
a9bee37e 1081 }
1082 }
a9bee37e 1083
1084 //
1085 // Overrides for the role assignments IN SUBCONTEXTS
1086 // (though we still do _not_ go below the course level.
1087 //
1088 // NOTE that the JOIN w sctx is with 3-way triangulation to
1089 // catch overrides to the applicable role in any subcontext, based
1090 // on the path field of the parent.
1091 //
1092 $sql = "SELECT sctx.path, ra.roleid,
1093 ctx.path AS parentpath,
1094 rco.capability, rco.permission
f33e1ed4 1095 FROM {role_assignments} ra
1096 JOIN {context} ctx
1097 ON ra.contextid=ctx.id
1098 JOIN {context} sctx
5a4e7398 1099 ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " )
f33e1ed4 1100 JOIN {role_capabilities} rco
1101 ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1102 WHERE ra.userid = ?
004865e2 1103 AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
1104 AND sctx.contextlevel <= ".CONTEXT_COURSE."
f33e1ed4 1105 ORDER BY sctx.depth, sctx.path, ra.roleid";
1106 $params = array($userid);
1107 $rs = $DB->get_recordset_sql($sql, $params);
0dbb2191 1108 if ($rs) {
f33e1ed4 1109 foreach ($rs as $rd) {
0dbb2191 1110 $k = "{$rd->path}:{$rd->roleid}";
1111 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1112 }
1113 unset($rd);
f33e1ed4 1114 $rs->close();
a9bee37e 1115 }
bb2c22bd 1116 return $accessdata;
a9bee37e 1117}
1118
74ac5b66 1119/**
46808d7c 1120 * Add to the access ctrl array the data needed by a user for a given context
74ac5b66 1121 *
cc3edaa4 1122 * @global object
1123 * @global object
46808d7c 1124 * @param integer $userid the id of the user
1125 * @param object $context needs path!
1126 * @param array $accessdata accessdata array
74ac5b66 1127 */
a2cf7f1b 1128function load_subcontext($userid, $context, &$accessdata) {
74ac5b66 1129
f33e1ed4 1130 global $CFG, $DB;
018d4b52 1131
1132 /* Get the additional RAs and relevant rolecaps
74ac5b66 1133 * - role assignments - with role_caps
1134 * - relevant role caps
1135 * - above this user's RAs
1136 * - below this user's RAs - limited to course level
1137 */
1138
74ac5b66 1139 $base = "/" . SYSCONTEXTID;
1140
53fb75dc 1141 //
1142 // Replace $context with the target context we will
1143 // load. Normally, this will be a course context, but
1144 // may be a different top-level context.
1145 //
1146 // We have 3 cases
74ac5b66 1147 //
1148 // - Course
1149 // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1150 // - BLOCK/MODULE/GROUP hanging from a course
1151 //
1152 // For course contexts, we _already_ have the RAs
1153 // but the cost of re-fetching is minimal so we don't care.
74ac5b66 1154 //
5a4e7398 1155 if ($context->contextlevel !== CONTEXT_COURSE
53fb75dc 1156 && $context->path !== "$base/{$context->id}") {
1157 // Case BLOCK/MODULE/GROUP hanging from a course
74ac5b66 1158 // Assumption: the course _must_ be our parent
1159 // If we ever see stuff nested further this needs to
1160 // change to do 1 query over the exploded path to
1161 // find out which one is the course
c2f10673 1162 $courses = explode('/',get_course_from_path($context->path));
1163 $targetid = array_pop($courses);
53fb75dc 1164 $context = get_context_instance_by_id($targetid);
5a4e7398 1165
74ac5b66 1166 }
1167
1168 //
53fb75dc 1169 // Role assignments in the context and below
74ac5b66 1170 //
53fb75dc 1171 $sql = "SELECT ctx.path, ra.roleid
f33e1ed4 1172 FROM {role_assignments} ra
1173 JOIN {context} ctx
1174 ON ra.contextid=ctx.id
1175 WHERE ra.userid = ?
1176 AND (ctx.path = ? OR ctx.path LIKE ?)
082e777a 1177 ORDER BY ctx.depth, ctx.path, ra.roleid";
f33e1ed4 1178 $params = array($userid, $context->path, $context->path."/%");
1179 $rs = $DB->get_recordset_sql($sql, $params);
74ac5b66 1180
5a4e7398 1181 //
082e777a 1182 // Read in the RAs, preventing duplicates
018d4b52 1183 //
f33e1ed4 1184 if ($rs) {
1185 $localroles = array();
082e777a 1186 $lastseen = '';
f33e1ed4 1187 foreach ($rs as $ra) {
1188 if (!isset($accessdata['ra'][$ra->path])) {
1189 $accessdata['ra'][$ra->path] = array();
1190 }
082e777a 1191 // only add if is not a repeat caused
1192 // by capability join...
1193 // (this check is cheaper than in_array())
1194 if ($lastseen !== $ra->path.':'.$ra->roleid) {
1195 $lastseen = $ra->path.':'.$ra->roleid;
1196 array_push($accessdata['ra'][$ra->path], $ra->roleid);
1197 array_push($localroles, $ra->roleid);
1198 }
74ac5b66 1199 }
f33e1ed4 1200 $rs->close();
74ac5b66 1201 }
74ac5b66 1202
018d4b52 1203 //
53fb75dc 1204 // Walk up and down the tree to grab all the roledefs
74ac5b66 1205 // of interest to our user...
018d4b52 1206 //
53fb75dc 1207 // NOTES
1208 // - we use IN() but the number of roles is very limited.
1209 //
bb2c22bd 1210 $courseroles = aggregate_roles_from_accessdata($context, $accessdata);
53fb75dc 1211
1212 // Do we have any interesting "local" roles?
1213 $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1214 $wherelocalroles='';
1215 if (count($localroles)) {
1216 // Role defs for local roles in 'higher' contexts...
1217 $contexts = substr($context->path, 1); // kill leading slash
1218 $contexts = str_replace('/', ',', $contexts);
1219 $localroleids = implode(',',$localroles);
5a4e7398 1220 $wherelocalroles="OR (rc.roleid IN ({$localroleids})
53fb75dc 1221 AND ctx.id IN ($contexts))" ;
74ac5b66 1222 }
1223
53fb75dc 1224 // We will want overrides for all of them
7e17f43b 1225 $whereroles = '';
1226 if ($roleids = implode(',',array_merge($courseroles,$localroles))) {
1227 $whereroles = "rc.roleid IN ($roleids) AND";
1228 }
53fb75dc 1229 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
f33e1ed4 1230 FROM {role_capabilities} rc
1231 JOIN {context} ctx
1232 ON rc.contextid=ctx.id
1233 WHERE ($whereroles
1234 (ctx.id=? OR ctx.path LIKE ?))
1235 $wherelocalroles
1236 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1237 $params = array($context->id, $context->path."/%");
53fb75dc 1238
a2cf7f1b 1239 $newrdefs = array();
f33e1ed4 1240 if ($rs = $DB->get_recordset_sql($sql, $params)) {
1241 foreach ($rs as $rd) {
03cedd62 1242 $k = "{$rd->path}:{$rd->roleid}";
a2cf7f1b 1243 if (!array_key_exists($k, $newrdefs)) {
1244 $newrdefs[$k] = array();
1245 }
1246 $newrdefs[$k][$rd->capability] = $rd->permission;
74ac5b66 1247 }
f33e1ed4 1248 $rs->close();
7e17f43b 1249 } else {
1250 debugging('Bad SQL encountered!');
74ac5b66 1251 }
74ac5b66 1252
a2cf7f1b 1253 compact_rdefs($newrdefs);
1254 foreach ($newrdefs as $key=>$value) {
1255 $accessdata['rdef'][$key] =& $newrdefs[$key];
1256 }
74ac5b66 1257
6100dad0 1258 // error_log("loaded {$context->path}");
bb2c22bd 1259 $accessdata['loaded'][] = $context->path;
74ac5b66 1260}
2f1a4248 1261
eef879ec 1262/**
46808d7c 1263 * Add to the access ctrl array the data needed by a role for a given context.
6f1bce30 1264 *
1265 * The data is added in the rdef key.
1266 *
1267 * This role-centric function is useful for role_switching
1268 * and to get an overview of what a role gets under a
1269 * given context and below...
1270 *
cc3edaa4 1271 * @global object
1272 * @global object
46808d7c 1273 * @param integer $roleid the id of the user
1274 * @param object $context needs path!
4f0c2d00 1275 * @param array $accessdata accessdata array NULL by default
46808d7c 1276 * @return array
6f1bce30 1277 */
bb2c22bd 1278function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
6f1bce30 1279
f33e1ed4 1280 global $CFG, $DB;
6f1bce30 1281
1282 /* Get the relevant rolecaps into rdef
1283 * - relevant role caps
1284 * - at ctx and above
1285 * - below this ctx
1286 */
1287
bb2c22bd 1288 if (is_null($accessdata)) {
1289 $accessdata = array(); // named list
1290 $accessdata['ra'] = array();
1291 $accessdata['rdef'] = array();
1292 $accessdata['loaded'] = array();
6f1bce30 1293 }
5a4e7398 1294
6f1bce30 1295 $contexts = substr($context->path, 1); // kill leading slash
1296 $contexts = str_replace('/', ',', $contexts);
1297
1298 //
1299 // Walk up and down the tree to grab all the roledefs
1300 // of interest to our role...
1301 //
1302 // NOTE: we use an IN clauses here - which
1303 // might explode on huge sites with very convoluted nesting of
1304 // categories... - extremely unlikely that the number of nested
1305 // categories is so large that we hit the limits of IN()
1306 //
1307 $sql = "SELECT ctx.path, rc.capability, rc.permission
f33e1ed4 1308 FROM {role_capabilities} rc
1309 JOIN {context} ctx
1310 ON rc.contextid=ctx.id
1311 WHERE rc.roleid=? AND
5a4e7398 1312 ( ctx.id IN ($contexts) OR
f33e1ed4 1313 ctx.path LIKE ? )
1314 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1315 $params = array($roleid, $context->path."/%");
1316
1317 if ($rs = $DB->get_recordset_sql($sql, $params)) {
1318 foreach ($rs as $rd) {
1319 $k = "{$rd->path}:{$roleid}";
1320 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1321 }
1322 $rs->close();
6f1bce30 1323 }
6f1bce30 1324
bb2c22bd 1325 return $accessdata;
6f1bce30 1326}
1327
a2cf7f1b 1328/**
46808d7c 1329 * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global
204a369c 1330 *
1331 * Used by has_capability() - but feel free
5a4e7398 1332 * to call it if you are about to run a BIG
204a369c 1333 * cron run across a bazillion users.
1334 *
cc3edaa4 1335 * @global object
1336 * @global object
46808d7c 1337 * @param int $userid
1338 * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid]
5a4e7398 1339 */
204a369c 1340function load_user_accessdata($userid) {
d867e696 1341 global $CFG, $ACCESSLIB_PRIVATE;
6f1bce30 1342
7293b3c6 1343 $base = '/'.SYSCONTEXTID;
204a369c 1344
bb2c22bd 1345 $accessdata = get_user_access_sitewide($userid);
5a4e7398 1346 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
3ac81bd1 1347 //
7293b3c6 1348 // provide "default role" & set 'dr'
3ac81bd1 1349 //
7d0c81b3 1350 if (!empty($CFG->defaultuserroleid)) {
1351 $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1352 if (!isset($accessdata['ra'][$base])) {
1353 $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1354 } else {
1355 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1356 }
1357 $accessdata['dr'] = $CFG->defaultuserroleid;
204a369c 1358 }
1359
4e1fe7d1 1360 //
1361 // provide "default frontpage role"
1362 //
3d811bc1 1363 if (!empty($CFG->defaultfrontpageroleid)) {
4e1fe7d1 1364 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
3d811bc1 1365 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
4e1fe7d1 1366 if (!isset($accessdata['ra'][$base])) {
3d811bc1 1367 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
4e1fe7d1 1368 } else {
3d811bc1 1369 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
4e1fe7d1 1370 }
1371 }
128f0984 1372 // for dirty timestamps in cron
1373 $accessdata['time'] = time();
1374
d867e696 1375 $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1376 compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']);
a2cf7f1b 1377
d867e696 1378 return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
204a369c 1379}
ef989bd9 1380
a2cf7f1b 1381/**
4f65e0fb 1382 * Use shared copy of role definitions stored in ACCESSLIB_PRIVATE->roledefinitions;
cc3edaa4 1383 *
1384 * @global object
a2cf7f1b 1385 * @param array $rdefs array of role definitions in contexts
1386 */
1387function compact_rdefs(&$rdefs) {
d867e696 1388 global $ACCESSLIB_PRIVATE;
a2cf7f1b 1389
1390 /*
1391 * This is a basic sharing only, we could also
1392 * use md5 sums of values. The main purpose is to
d867e696 1393 * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array.
a2cf7f1b 1394 */
1395
1396 foreach ($rdefs as $key => $value) {
d867e696 1397 if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) {
1398 $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key];
a2cf7f1b 1399 }
d867e696 1400 $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key];
a2cf7f1b 1401 }
1402}
1403
6f1bce30 1404/**
46808d7c 1405 * A convenience function to completely load all the capabilities
1406 * for the current user. This is what gets called from complete_user_login()
1407 * for example. Call it only _after_ you've setup $USER and called
1408 * check_enrolment_plugins();
1409 * @see check_enrolment_plugins()
117bd748 1410 *
cc3edaa4 1411 * @global object
1412 * @global object
1413 * @global object
2f1a4248 1414 */
1415function load_all_capabilities() {
b545e27a
PS
1416 global $CFG, $ACCESSLIB_PRIVATE;
1417
1418 //NOTE: we can not use $USER here because it may no be linked to $_SESSION['USER'] yet!
bbbf2d40 1419
18818abf 1420 // roles not installed yet - we are in the middle of installation
31a99877 1421 if (during_initial_install()) {
1045a007 1422 return;
1423 }
1424
e0376a62 1425 $base = '/'.SYSCONTEXTID;
1426
b545e27a 1427 if (isguestuser($_SESSION['USER'])) {
e0376a62 1428 $guest = get_guest_role();
1429
1430 // Load the rdefs
b545e27a 1431 $_SESSION['USER']->access = get_role_access($guest->id);
e0376a62 1432 // Put the ghost enrolment in place...
b545e27a 1433 $_SESSION['USER']->access['ra'][$base] = array($guest->id);
eef879ec 1434
7293b3c6 1435
b545e27a 1436 } else if (empty($_SESSION['USER']->id)) { // can not use isloggedin() yet
eef879ec 1437
b545e27a 1438 $accessdata = get_user_access_sitewide($_SESSION['USER']->id);
3887fe4a 1439
7293b3c6 1440 //
1441 // provide "default role" & set 'dr'
1442 //
7d0c81b3 1443 if (!empty($CFG->defaultuserroleid)) {
1444 $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1445 if (!isset($accessdata['ra'][$base])) {
1446 $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1447 } else {
1448 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1449 }
1450 $accessdata['dr'] = $CFG->defaultuserroleid;
c0aa9f09 1451 }
7293b3c6 1452
4e1fe7d1 1453 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
de5e137a 1454
4e1fe7d1 1455 //
1456 // provide "default frontpage role"
1457 //
3d811bc1 1458 if (!empty($CFG->defaultfrontpageroleid)) {
4e1fe7d1 1459 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1460 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1461 if (!isset($accessdata['ra'][$base])) {
1462 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1463 } else {
1464 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1465 }
5a4e7398 1466 }
b545e27a 1467 $_SESSION['USER']->access = $accessdata;
5a4e7398 1468
7d0c81b3 1469 } else if (!empty($CFG->notloggedinroleid)) {
b545e27a
PS
1470 $_SESSION['USER']->access = get_role_access($CFG->notloggedinroleid);
1471 $_SESSION['USER']->access['ra'][$base] = array($CFG->notloggedinroleid);
2f1a4248 1472 }
55e68c29 1473
128f0984 1474 // Timestamp to read dirty context timestamps later
b545e27a 1475 $_SESSION['USER']->access['time'] = time();
d867e696 1476 $ACCESSLIB_PRIVATE->dirtycontexts = array();
55e68c29 1477
1478 // Clear to force a refresh
b545e27a 1479 unset($_SESSION['USER']->mycourses);
bbbf2d40 1480}
1481
ef989bd9 1482/**
5a4e7398 1483 * A convenience function to completely reload all the capabilities
ef989bd9 1484 * for the current user when roles have been updated in a relevant
5a4e7398 1485 * context -- but PRESERVING switchroles and loginas.
ef989bd9 1486 *
1487 * That is - completely transparent to the user.
5a4e7398 1488 *
ef989bd9 1489 * Note: rewrites $USER->access completely.
1490 *
cc3edaa4 1491 * @global object
1492 * @global object
ef989bd9 1493 */
1494function reload_all_capabilities() {
f33e1ed4 1495 global $USER, $DB;
ef989bd9 1496
b738808b 1497 // error_log("reloading");
ef989bd9 1498 // copy switchroles
1499 $sw = array();
1500 if (isset($USER->access['rsw'])) {
1501 $sw = $USER->access['rsw'];
b738808b 1502 // error_log(print_r($sw,1));
ef989bd9 1503 }
1504
1505 unset($USER->access);
54f9d9ae 1506 unset($USER->mycourses);
5a4e7398 1507
ef989bd9 1508 load_all_capabilities();
1509
1510 foreach ($sw as $path => $roleid) {
f33e1ed4 1511 $context = $DB->get_record('context', array('path'=>$path));
ef989bd9 1512 role_switch($roleid, $context);
1513 }
1514
1515}
2f1a4248 1516
f33e1ed4 1517/**
343effbe 1518 * Adds a temp role to an accessdata array.
1519 *
1520 * Useful for the "temporary guest" access
1521 * we grant to logged-in users.
1522 *
1523 * Note - assumes a course context!
1524 *
46808d7c 1525 * @param object $content
1526 * @param int $roleid
1527 * @param array $accessdata
1528 * @return array Returns access data
343effbe 1529 */
df997f84 1530function load_temp_role($context, $roleid, array $accessdata) {
f33e1ed4 1531 global $CFG, $DB;
343effbe 1532
1533 //
1534 // Load rdefs for the role in -
1535 // - this context
1536 // - all the parents
1537 // - and below - IOWs overrides...
1538 //
5a4e7398 1539
343effbe 1540 // turn the path into a list of context ids
1541 $contexts = substr($context->path, 1); // kill leading slash
1542 $contexts = str_replace('/', ',', $contexts);
1543
f33e1ed4 1544 $sql = "SELECT ctx.path, rc.capability, rc.permission
1545 FROM {context} ctx
1546 JOIN {role_capabilities} rc
1547 ON rc.contextid=ctx.id
1548 WHERE (ctx.id IN ($contexts)
1549 OR ctx.path LIKE ?)
1550 AND rc.roleid = ?
1551 ORDER BY ctx.depth, ctx.path";
5a4e7398 1552 $params = array($context->path."/%", $roleid);
1553 if ($rs = $DB->get_recordset_sql($sql, $params)) {
f33e1ed4 1554 foreach ($rs as $rd) {
1555 $k = "{$rd->path}:{$roleid}";
1556 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1557 }
3bea11c8 1558 $rs->close();
f33e1ed4 1559 }
343effbe 1560
1561 //
1562 // Say we loaded everything for the course context
1563 // - which we just did - if the user gets a proper
1564 // RA in this session, this data will need to be reloaded,
1565 // but that is handled by the complete accessdata reload
1566 //
bb2c22bd 1567 array_push($accessdata['loaded'], $context->path);
343effbe 1568
1569 //
1570 // Add the ghost RA
1571 //
bb2c22bd 1572 if (isset($accessdata['ra'][$context->path])) {
1573 array_push($accessdata['ra'][$context->path], $roleid);
343effbe 1574 } else {
bb2c22bd 1575 $accessdata['ra'][$context->path] = array($roleid);
343effbe 1576 }
1577
bb2c22bd 1578 return $accessdata;
343effbe 1579}
1580
efe12f6c 1581/**
df997f84
PS
1582 * Removes any extra guest roels from accessdata
1583 * @param object $context
1584 * @param array $accessdata
1585 * @return array access data
64026e8c 1586 */
df997f84
PS
1587function remove_temp_roles($context, array $accessdata) {
1588 global $DB, $USER;
1589 $sql = "SELECT DISTINCT ra.roleid AS id
1590 FROM {role_assignments} ra
1591 WHERE ra.contextid = :contextid AND ra.userid = :userid";
1592 $ras = $DB->get_records_sql($sql, array('contextid'=>$context->id, 'userid'=>$USER->id));
e4ec4e41 1593
df997f84
PS
1594 $accessdata['ra'][$context->path] = array_keys($ras);
1595 return $accessdata;
64026e8c 1596}
1597
3562486b 1598/**
4f0c2d00 1599 * Returns array of all role archetypes.
cc3edaa4 1600 *
46808d7c 1601 * @return array
3562486b 1602 */
4f0c2d00 1603function get_role_archetypes() {
3562486b 1604 return array(
4f0c2d00
PS
1605 'manager' => 'manager',
1606 'coursecreator' => 'coursecreator',
1607 'editingteacher' => 'editingteacher',
1608 'teacher' => 'teacher',
1609 'student' => 'student',
1610 'guest' => 'guest',
1611 'user' => 'user',
1612 'frontpage' => 'frontpage'
3562486b 1613 );
1614}
1615
bbbf2d40 1616/**
4f65e0fb 1617 * Assign the defaults found in this capability definition to roles that have
bbbf2d40 1618 * the corresponding legacy capabilities assigned to them.
cc3edaa4 1619 *
46808d7c 1620 * @param string $capability
1621 * @param array $legacyperms an array in the format (example):
bbbf2d40 1622 * 'guest' => CAP_PREVENT,
1623 * 'student' => CAP_ALLOW,
1624 * 'teacher' => CAP_ALLOW,
1625 * 'editingteacher' => CAP_ALLOW,
1626 * 'coursecreator' => CAP_ALLOW,
4f0c2d00 1627 * 'manager' => CAP_ALLOW
46808d7c 1628 * @return boolean success or failure.
bbbf2d40 1629 */
1630function assign_legacy_capabilities($capability, $legacyperms) {
eef868d1 1631
4f0c2d00 1632 $archetypes = get_role_archetypes();
3562486b 1633
bbbf2d40 1634 foreach ($legacyperms as $type => $perm) {
eef868d1 1635
21c9bace 1636 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
4f0c2d00
PS
1637 if ($type === 'admin') {
1638 debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1639 $type = 'manager';
1640 }
eef868d1 1641
4f0c2d00 1642 if (!array_key_exists($type, $archetypes)) {
e49ef64a 1643 print_error('invalidlegacy', '', '', $type);
3562486b 1644 }
eef868d1 1645
4f0c2d00 1646 if ($roles = get_archetype_roles($type)) {
2e85fffe 1647 foreach ($roles as $role) {
1648 // Assign a site level capability.
1649 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1650 return false;
1651 }
bbbf2d40 1652 }
1653 }
1654 }
1655 return true;
1656}
1657
faf75fe7 1658/**
ed149942
PS
1659 * @param object $capability a capability - a row from the capabilities table.
1660 * @return boolean whether this capability is safe - that is, whether people with the
faf75fe7 1661 * safeoverrides capability should be allowed to change it.
1662 */
1663function is_safe_capability($capability) {
4659454a 1664 return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
faf75fe7 1665}
cee0901c 1666
1667/**********************************
bbbf2d40 1668 * Context Manipulation functions *
1669 **********************************/
1670
bbbf2d40 1671/**
a0760047 1672 * Context creation - internal implementation.
46808d7c 1673 *
9991d157 1674 * Create a new context record for use by all roles-related stuff
4881f2d3 1675 * assumes that the caller has done the homework.
1676 *
a0760047
PS
1677 * DO NOT CALL THIS DIRECTLY, instead use {@link get_context_instance}!
1678 *
46808d7c 1679 * @param int $contextlevel
1680 * @param int $instanceid
4f0c2d00 1681 * @param int $strictness
e40413be 1682 * @return object newly created context
bbbf2d40 1683 */
4f0c2d00 1684function create_context($contextlevel, $instanceid, $strictness=IGNORE_MISSING) {
e40413be 1685
5a4e7398 1686 global $CFG, $DB;
e40413be 1687
4881f2d3 1688 if ($contextlevel == CONTEXT_SYSTEM) {
1689 return create_system_context();
1690 }
c421ad4b 1691
4881f2d3 1692 $context = new object();
1693 $context->contextlevel = $contextlevel;
1694 $context->instanceid = $instanceid;
e40413be 1695
1696 // Define $context->path based on the parent
1697 // context. In other words... Who is your daddy?
ca92b391 1698 $basepath = '/' . SYSCONTEXTID;
1699 $basedepth = 1;
e40413be 1700
7d0c81b3 1701 $result = true;
4f0c2d00 1702 $error_message = NULL;
7d0c81b3 1703
e40413be 1704 switch ($contextlevel) {
1705 case CONTEXT_COURSECAT:
5a4e7398 1706 $sql = "SELECT ctx.path, ctx.depth
1707 FROM {context} ctx
1708 JOIN {course_categories} cc
1709 ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1710 WHERE cc.id=?";
1711 $params = array($instanceid);
1712 if ($p = $DB->get_record_sql($sql, $params)) {
ca92b391 1713 $basepath = $p->path;
1714 $basedepth = $p->depth;
4f0c2d00 1715 } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), '*', $strictness)) {
7d0c81b3 1716 if (empty($category->parent)) {
1717 // ok - this is a top category
1718 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
1719 $basepath = $parent->path;
1720 $basedepth = $parent->depth;
1721 } else {
1722 // wrong parent category - no big deal, this can be fixed later
4f0c2d00 1723 $basepath = NULL;
7d0c81b3 1724 $basedepth = 0;
1725 }
1726 } else {
1727 // incorrect category id
f689028c 1728 $error_message = "incorrect course category id ($instanceid)";
7d0c81b3 1729 $result = false;
e40413be 1730 }
1731 break;
1732
1733 case CONTEXT_COURSE:
ca92b391 1734 $sql = "SELECT ctx.path, ctx.depth
5a4e7398 1735 FROM {context} ctx
1736 JOIN {course} c
1737 ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1738 WHERE c.id=? AND c.id !=" . SITEID;
1739 $params = array($instanceid);
1740 if ($p = $DB->get_record_sql($sql, $params)) {
ca92b391 1741 $basepath = $p->path;
1742 $basedepth = $p->depth;
4f0c2d00 1743 } else if ($course = $DB->get_record('course', array('id'=>$instanceid), '*', $strictness)) {
7d0c81b3 1744 if ($course->id == SITEID) {
1745 //ok - no parent category
1746 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
1747 $basepath = $parent->path;
1748 $basedepth = $parent->depth;
1749 } else {
1750 // wrong parent category of course - no big deal, this can be fixed later
4f0c2d00 1751 $basepath = NULL;
7d0c81b3 1752 $basedepth = 0;
1753 }
1754 } else if ($instanceid == SITEID) {
1755 // no errors for missing site course during installation
1756 return false;
1757 } else {
1758 // incorrect course id
f689028c 1759 $error_message = "incorrect course id ($instanceid)";
7d0c81b3 1760 $result = false;
e40413be 1761 }
1762 break;
1763
1764 case CONTEXT_MODULE:
ca92b391 1765 $sql = "SELECT ctx.path, ctx.depth
5a4e7398 1766 FROM {context} ctx
1767 JOIN {course_modules} cm
1768 ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1769 WHERE cm.id=?";
1770 $params = array($instanceid);
1771 if ($p = $DB->get_record_sql($sql, $params)) {
7d0c81b3 1772 $basepath = $p->path;
1773 $basedepth = $p->depth;
4f0c2d00
PS
1774 } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), '*', $strictness)) {
1775 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course, $strictness)) {
7d0c81b3 1776 $basepath = $parent->path;
1777 $basedepth = $parent->depth;
1778 } else {
1779 // course does not exist - modules can not exist without a course
f689028c 1780 $error_message = "course does not exist ($cm->course) - modules can not exist without a course";
7d0c81b3 1781 $result = false;
1782 }
1783 } else {
1784 // cm does not exist
837e6a44 1785 $error_message = "cm with id $instanceid does not exist";
7d0c81b3 1786 $result = false;
1787 }
e40413be 1788 break;
1789
1790 case CONTEXT_BLOCK:
ca92b391 1791 $sql = "SELECT ctx.path, ctx.depth
f474a4e5 1792 FROM {context} ctx
13a0d3d3 1793 JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id)
e92c286c 1794 WHERE bi.id = ?";
f474a4e5 1795 $params = array($instanceid, CONTEXT_COURSE);
4f0c2d00 1796 if ($p = $DB->get_record_sql($sql, $params, '*', $strictness)) {
ca92b391 1797 $basepath = $p->path;
1798 $basedepth = $p->depth;
7d0c81b3 1799 } else {
1800 // block does not exist
f474a4e5 1801 $error_message = 'block or parent context does not exist';
7d0c81b3 1802 $result = false;
ca92b391 1803 }
e40413be 1804 break;
1805 case CONTEXT_USER:
1806 // default to basepath
1807 break;
e40413be 1808 }
1809
7d0c81b3 1810 // if grandparents unknown, maybe rebuild_context_path() will solve it later
1811 if ($basedepth != 0) {
1812 $context->depth = $basedepth+1;
1813 }
1814
4f0c2d00 1815 if (!$result) {
4881f2d3 1816 debugging('Error: could not insert new context level "'.
1817 s($contextlevel).'", instance "'.
f689028c 1818 s($instanceid).'". ' . $error_message);
1819
7d0c81b3 1820 return false;
bbbf2d40 1821 }
4f0c2d00
PS
1822
1823 $id = $DB->insert_record('context', $context);
1824 // can't set the full path till we know the id!
1825 if ($basedepth != 0 and !empty($basepath)) {
1826 $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
1827 }
1828 return get_context_instance_by_id($id);
bbbf2d40 1829}
1830
efe12f6c 1831/**
4f0c2d00 1832 * Returns system context or NULL if can not be created yet.
46808d7c 1833 *
1834 * @todo can not use get_record() because we do not know if query failed :-(
1835 * switch to get_record() later
1836 *
cc3edaa4 1837 * @global object
1838 * @global object
0ecff22d 1839 * @param bool $cache use caching
4f0c2d00 1840 * @return mixed system context or NULL
8ba412da 1841 */
7d0c81b3 1842function get_system_context($cache=true) {
d867e696 1843 global $DB, $ACCESSLIB_PRIVATE;
7d0c81b3 1844 if ($cache and defined('SYSCONTEXTID')) {
d867e696 1845 if (is_null($ACCESSLIB_PRIVATE->systemcontext)) {
1846 $ACCESSLIB_PRIVATE->systemcontext = new object();
1847 $ACCESSLIB_PRIVATE->systemcontext->id = SYSCONTEXTID;
1848 $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM;
1849 $ACCESSLIB_PRIVATE->systemcontext->instanceid = 0;
1850 $ACCESSLIB_PRIVATE->systemcontext->path = '/'.SYSCONTEXTID;
1851 $ACCESSLIB_PRIVATE->systemcontext->depth = 1;
7d0c81b3 1852 }
d867e696 1853 return $ACCESSLIB_PRIVATE->systemcontext;
7d0c81b3 1854 }
c23b0ea1 1855 try {
df97c6ee 1856 $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM));
0ecff22d 1857 } catch (dml_exception $e) {
c23b0ea1 1858 //table does not exist yet, sorry
4f0c2d00 1859 return NULL;
c23b0ea1 1860 }
1861
1862 if (!$context) {
8ba412da 1863 $context = new object();
1864 $context->contextlevel = CONTEXT_SYSTEM;
7d0c81b3 1865 $context->instanceid = 0;
1866 $context->depth = 1;
1867 $context->path = NULL; //not known before insert
1868
0ecff22d 1869 try {
a8f3a651 1870 $context->id = $DB->insert_record('context', $context);
0ecff22d 1871 } catch (dml_exception $e) {
a8f3a651 1872 // can not create context yet, sorry
4f0c2d00 1873 return NULL;
8ba412da 1874 }
1875 }
7d0c81b3 1876
1877 if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
1878 $context->instanceid = 0;
1879 $context->path = '/'.$context->id;
1880 $context->depth = 1;
5a4e7398 1881 $DB->update_record('context', $context);
7d0c81b3 1882 }
1883
1884 if (!defined('SYSCONTEXTID')) {
1885 define('SYSCONTEXTID', $context->id);
1886 }
1887
d867e696 1888 $ACCESSLIB_PRIVATE->systemcontext = $context;
1889 return $ACCESSLIB_PRIVATE->systemcontext;
8ba412da 1890}
b51ece5b 1891
9991d157 1892/**
b51ece5b 1893 * Remove a context record and any dependent entries,
1894 * removes context from static context cache too
cc3edaa4 1895 *
46808d7c 1896 * @param int $level
1897 * @param int $instanceid
196f1a25 1898 * @return bool returns true or throws an exception
9991d157 1899 */
1900function delete_context($contextlevel, $instanceid) {
8432f5e6 1901 global $DB, $ACCESSLIB_PRIVATE, $CFG;
b51ece5b 1902
1903 // do not use get_context_instance(), because the related object might not exist,
1904 // or the context does not exist yet and it would be created now
5a4e7398 1905 if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
196f1a25
PS
1906 $DB->delete_records('role_assignments', array('contextid'=>$context->id));
1907 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
1908 $DB->delete_records('context', array('id'=>$context->id));
1909 $DB->delete_records('role_names', array('contextid'=>$context->id));
b51ece5b 1910
a05bcfba
PS
1911 // delete all files attached to this context
1912 $fs = get_file_storage();
1913 $fs->delete_area_files($context->id);
1914
b51ece5b 1915 // do not mark dirty contexts if parents unknown
1916 if (!is_null($context->path) and $context->depth > 0) {
1917 mark_context_dirty($context->path);
1918 }
1919
1920 // purge static context cache if entry present
d867e696 1921 unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]);
1922 unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]);
b51ece5b 1923
6bf44482 1924 blocks_delete_all_for_context($context->id);
9434fef4 1925 filter_delete_all_for_context($context->id);
9991d157 1926 }
196f1a25
PS
1927
1928 return true;
9991d157 1929}
1930
9a81a606 1931/**
1932 * Precreates all contexts including all parents
cc3edaa4 1933 *
1934 * @global object
46808d7c 1935 * @param int $contextlevel empty means all
9a81a606 1936 * @param bool $buildpaths update paths and depths
1937 * @return void
1938 */
4f0c2d00 1939function create_contexts($contextlevel=NULL, $buildpaths=true) {
5a4e7398 1940 global $DB;
9a81a606 1941
1942 //make sure system context exists
1943 $syscontext = get_system_context(false);
1944
5c8e6cb1 1945 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
1946 or $contextlevel == CONTEXT_COURSE
1947 or $contextlevel == CONTEXT_MODULE
1948 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1949 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1950 SELECT ".CONTEXT_COURSECAT.", cc.id
5a4e7398 1951 FROM {course}_categories cc
9a81a606 1952 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1953 FROM {context} cx
9a81a606 1954 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
5a4e7398 1955 $DB->execute($sql);
9a81a606 1956
1957 }
1958
5c8e6cb1 1959 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
1960 or $contextlevel == CONTEXT_MODULE
1961 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1962 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1963 SELECT ".CONTEXT_COURSE.", c.id
5a4e7398 1964 FROM {course} c
9a81a606 1965 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1966 FROM {context} cx
9a81a606 1967 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
5a4e7398 1968 $DB->execute($sql);
9a81a606 1969
1970 }
1971
e92c286c 1972 if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
1973 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1974 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1975 SELECT ".CONTEXT_MODULE.", cm.id
5a4e7398 1976 FROM {course}_modules cm
9a81a606 1977 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1978 FROM {context} cx
9a81a606 1979 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
5a4e7398 1980 $DB->execute($sql);
9a81a606 1981 }
1982
e92c286c 1983 if (empty($contextlevel) or $contextlevel == CONTEXT_USER
1984 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1985 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1986 SELECT ".CONTEXT_USER.", u.id
5a4e7398 1987 FROM {user} u
9a81a606 1988 WHERE u.deleted=0
1989 AND NOT EXISTS (SELECT 'x'
5a4e7398 1990 FROM {context} cx
9a81a606 1991 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
5a4e7398 1992 $DB->execute($sql);
9a81a606 1993
1994 }
1995
e92c286c 1996 if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
1997 $sql = "INSERT INTO {context} (contextlevel, instanceid)
1998 SELECT ".CONTEXT_BLOCK.", bi.id
1999 FROM {block_instances} bi
2000 WHERE NOT EXISTS (SELECT 'x'
2001 FROM {context} cx
2002 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2003 $DB->execute($sql);
2004 }
2005
9a81a606 2006 if ($buildpaths) {
5a4e7398 2007 build_context_path(false);
9a81a606 2008 }
2009}
2010
17b0efae 2011/**
2012 * Remove stale context records
2013 *
cc3edaa4 2014 * @global object
17b0efae 2015 * @return bool
2016 */
2017function cleanup_contexts() {
5a4e7398 2018 global $DB;
17b0efae 2019
70dd126e 2020 $sql = " SELECT c.contextlevel,
17b0efae 2021 c.instanceid AS instanceid
5a4e7398 2022 FROM {context} c
2023 LEFT OUTER JOIN {course}_categories t
2024 ON c.instanceid = t.id
2025 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
17b0efae 2026 UNION
70dd126e 2027 SELECT c.contextlevel,
2028 c.instanceid
5a4e7398 2029 FROM {context} c
2030 LEFT OUTER JOIN {course} t
2031 ON c.instanceid = t.id
2032 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
17b0efae 2033 UNION
70dd126e 2034 SELECT c.contextlevel,
2035 c.instanceid
5a4e7398 2036 FROM {context} c
2037 LEFT OUTER JOIN {course}_modules t
2038 ON c.instanceid = t.id
2039 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
17b0efae 2040 UNION
70dd126e 2041 SELECT c.contextlevel,
2042 c.instanceid
5a4e7398 2043 FROM {context} c
2044 LEFT OUTER JOIN {user} t
2045 ON c.instanceid = t.id
2046 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
17b0efae 2047 UNION
70dd126e 2048 SELECT c.contextlevel,
2049 c.instanceid
5a4e7398 2050 FROM {context} c
f474a4e5 2051 LEFT OUTER JOIN {block_instances} t
2052 ON c.instanceid = t.id
5a4e7398 2053 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
17b0efae 2054 ";
d5a8d9aa
PS
2055
2056 // transactions used only for performance reasons here
2057 $transaction = $DB->start_delegated_transaction();
2058
5a4e7398 2059 if ($rs = $DB->get_recordset_sql($sql)) {
5a4e7398 2060 foreach ($rs as $ctx) {
d5a8d9aa 2061 delete_context($ctx->contextlevel, $ctx->instanceid);
17b0efae 2062 }
5a4e7398 2063 $rs->close();
17b0efae 2064 }
d5a8d9aa
PS
2065
2066 $transaction->allow_commit();
17b0efae 2067 return true;
2068}
2069
00653161 2070/**
e92c286c 2071 * Preloads all contexts relating to a course: course, modules. Block contexts
2072 * are no longer loaded here. The contexts for all the blocks on the current
2073 * page are now efficiently loaded by {@link block_manager::load_blocks()}.
00653161 2074 *
2075 * @param int $courseid Course ID
d993468d 2076 * @return void
00653161 2077 */
2078function preload_course_contexts($courseid) {
d867e696 2079 global $DB, $ACCESSLIB_PRIVATE;
00653161 2080
2081 // Users can call this multiple times without doing any harm
d867e696 2082 global $ACCESSLIB_PRIVATE;
2083 if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
00653161 2084 return;
2085 }
2086
d993468d 2087 $params = array($courseid, $courseid, $courseid);
2088 $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2089 FROM {course_modules} cm
2090 JOIN {context} x ON x.instanceid=cm.id
2091 WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2092
d993468d 2093 UNION ALL
2094
2095 SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2096 FROM {context} x
2097 WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2098
2099 $rs = $DB->get_recordset_sql($sql, $params);
00653161 2100 foreach($rs as $context) {
d867e696 2101 cache_context($context);
00653161 2102 }
2103 $rs->close();
d867e696 2104 $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
00653161 2105}
2106
bbbf2d40 2107/**
2108 * Get the context instance as an object. This function will create the
2109 * context instance if it does not exist yet.
46808d7c 2110 *
2111 * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2112 *
e765b5d3 2113 * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2114 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
46808d7c 2115 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
4f0c2d00
PS
2116 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2117 * MUST_EXIST means throw exception if no record or multiple records found
e765b5d3 2118 * @return object The context object.
bbbf2d40 2119 */
4f0c2d00 2120function get_context_instance($contextlevel, $instance=0, $strictness=IGNORE_MISSING) {
e5605780 2121
d867e696 2122 global $DB, $ACCESSLIB_PRIVATE;
8ead7b59 2123 static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
d9a35e12 2124
7d0c81b3 2125/// System context has special cache
8ba412da 2126 if ($contextlevel == CONTEXT_SYSTEM) {
7d0c81b3 2127 return get_system_context();
8ba412da 2128 }
2129
a36a3a3f 2130/// check allowed context levels
2131 if (!in_array($contextlevel, $allowed_contexts)) {
7bfa3101 2132 // fatal error, code must be fixed - probably typo or switched parameters
e49ef64a 2133 print_error('invalidcourselevel');
a36a3a3f 2134 }
2135
65bcf17b 2136 if (!is_array($instance)) {
2137 /// Check the cache
d867e696 2138 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2139 return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2140 }
2141
2142 /// Get it from the database, or create it
5a4e7398 2143 if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
4f0c2d00 2144 $context = create_context($contextlevel, $instance, $strictness);
65bcf17b 2145 }
2146
2147 /// Only add to cache if context isn't empty.
2148 if (!empty($context)) {
d867e696 2149 cache_context($context);
65bcf17b 2150 }
2151
2152 return $context;
e5605780 2153 }
2154
65bcf17b 2155
2156/// ok, somebody wants to load several contexts to save some db queries ;-)
2157 $instances = $instance;
2158 $result = array();
2159
2160 foreach ($instances as $key=>$instance) {
2161 /// Check the cache first
d867e696 2162 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2163 $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2164 unset($instances[$key]);
2165 continue;
2166 }
e5605780 2167 }
2168
65bcf17b 2169 if ($instances) {
5a4e7398 2170 list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2171 array_unshift($params, $contextlevel);
2172 $sql = "SELECT instanceid, id, contextlevel, path, depth
2173 FROM {context}
2174 WHERE contextlevel=? AND instanceid $instanceids";
2175
2176 if (!$contexts = $DB->get_records_sql($sql, $params)) {
65bcf17b 2177 $contexts = array();
2178 }
2179
2180 foreach ($instances as $instance) {
2181 if (isset($contexts[$instance])) {
2182 $context = $contexts[$instance];
2183 } else {
2184 $context = create_context($contextlevel, $instance);
2185 }
2186
2187 if (!empty($context)) {
d867e696 2188 cache_context($context);
65bcf17b 2189 }
2190
2191 $result[$instance] = $context;
2192 }
ccfc5ecc 2193 }
0468976c 2194
65bcf17b 2195 return $result;
bbbf2d40 2196}
2197
cee0901c 2198
340ea4e8 2199/**
e765b5d3 2200 * Get a context instance as an object, from a given context id.
cc3edaa4 2201 *
65bcf17b 2202 * @param mixed $id a context id or array of ids.
01a2ce80
PS
2203 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2204 * MUST_EXIST means throw exception if no record or multiple records found
46808d7c 2205 * @return mixed object, array of the context object, or false.
340ea4e8 2206 */
01a2ce80 2207function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) {
d867e696 2208 global $DB, $ACCESSLIB_PRIVATE;
d9a35e12 2209
7d0c81b3 2210 if ($id == SYSCONTEXTID) {
2211 return get_system_context();
2212 }
2213
d867e696 2214 if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) { // Already cached
2215 return $ACCESSLIB_PRIVATE->contextsbyid[$id];
340ea4e8 2216 }
2217
01a2ce80 2218 if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
d867e696 2219 cache_context($context);
340ea4e8 2220 return $context;
2221 }
2222
2223 return false;
2224}
2225
bbbf2d40 2226
8737be58 2227/**
2228 * Get the local override (if any) for a given capability in a role in a context
cc3edaa4 2229 *
2230 * @global object
46808d7c 2231 * @param int $roleid
2232 * @param int $contextid
2233 * @param string $capability
8737be58 2234 */
2235function get_local_override($roleid, $contextid, $capability) {
5a4e7398 2236 global $DB;
2237 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
8737be58 2238}
2239
01a2ce80
PS
2240/**
2241 * Returns context instance plus related course and cm instances
2242 * @param int $contextid
2243 * @return array of ($context, $course, $cm)
2244 */
2245function get_context_info_array($contextid) {
2246 global $DB;
2247
2248 $context = get_context_instance_by_id($contextid, MUST_EXIST);
4f0c2d00
PS
2249 $course = NULL;
2250 $cm = NULL;
01a2ce80
PS
2251
2252 if ($context->contextlevel == CONTEXT_COURSE) {
2253 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2254
2255 } else if ($context->contextlevel == CONTEXT_MODULE) {
2256 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2257 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2258
2259 } else if ($context->contextlevel == CONTEXT_BLOCK) {
2260 $parentcontexts = get_parent_contexts($context, false);
2261 $parent = reset($parentcontexts);
2262 $parent = get_context_instance_by_id($parent);
2263
2264 if ($parent->contextlevel == CONTEXT_COURSE) {
2265 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2266 } else if ($parent->contextlevel == CONTEXT_MODULE) {
2267 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2268 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2269 }
2270 }
2271
2272 return array($context, $course, $cm);
2273}
8737be58 2274
35716b86
PS
2275/**
2276 * Returns current course id or null if outside of course based on context parameter.
2277 * @param object $context
2278 * @return int|bool related course id or false
2279 */
2280function get_courseid_from_context($context) {
2281 if ($context->contextlevel == CONTEXT_COURSE) {
2282 return $context->instanceid;
2283 }
2284
2285 if ($context->contextlevel < CONTEXT_COURSE) {
2286 return false;
2287 }
2288
2289 if ($context->contextlevel == CONTEXT_MODULE) {
2290 $parentcontexts = get_parent_contexts($context, false);
2291 $parent = reset($parentcontexts);
2292 $parent = get_context_instance_by_id($parent);
2293 return $parent->instanceid;
2294 }
2295
2296 if ($context->contextlevel == CONTEXT_BLOCK) {
2297 $parentcontexts = get_parent_contexts($context, false);
2298 $parent = reset($parentcontexts);
2299 return get_courseid_from_context($parent);
2300 }
2301
2302 return false;
2303}
2304
bbbf2d40 2305
46808d7c 2306//////////////////////////////////////
2307// DB TABLE RELATED FUNCTIONS //
2308//////////////////////////////////////
bbbf2d40 2309
cee0901c 2310/**
bbbf2d40 2311 * function that creates a role
cc3edaa4 2312 *
2313 * @global object
46808d7c 2314 * @param string $name role name
2315 * @param string $shortname role short name
2316 * @param string $description role description
4f0c2d00 2317 * @param string $archetype
46808d7c 2318 * @return mixed id or dml_exception
bbbf2d40 2319 */
4f0c2d00 2320function create_role($name, $shortname, $description, $archetype='') {
f33e1ed4 2321 global $DB;
eef868d1 2322
4f0c2d00
PS
2323 if (strpos($archetype, 'moodle/legacy:') !== false) {
2324 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
2325 }
2326
2327 // verify role archetype actually exists
2328 $archetypes = get_role_archetypes();
2329 if (empty($archetypes[$archetype])) {
2330 $archetype = '';
2331 }
2332
bbdb7070 2333 // Get the system context.
19a4a32e 2334 $context = get_context_instance(CONTEXT_SYSTEM);
31f26796 2335
bbdb7070 2336 // Insert the role record.
b5959f30 2337 $role = new object();
ac173d3e 2338 $role->name = $name;
2339 $role->shortname = $shortname;
98882637 2340 $role->description = $description;
4f0c2d00 2341 $role->archetype = $archetype;
eef868d1 2342
8420bee9 2343 //find free sortorder number
bbdb7070 2344 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
716dd163 2345 if (empty($role->sortorder)) {
2346 $role->sortorder = 1;
2347 }
bbdb7070 2348 $id = $DB->insert_record('role', $role);
b5959f30 2349
bbdb7070 2350 return $id;
bbbf2d40 2351}
2352
8420bee9 2353/**
46808d7c 2354 * Function that deletes a role and cleanups up after it
cc3edaa4 2355 *
46808d7c 2356 * @param int $roleid id of role to delete
4f65e0fb 2357 * @return bool always true
8420bee9 2358 */
2359function delete_role($roleid) {
f33e1ed4 2360 global $CFG, $DB;
c421ad4b 2361
4f0c2d00 2362 // first unssign all users
df997f84 2363 role_unassign_all(array('roleid'=>$roleid));
c421ad4b 2364
4f0c2d00
PS
2365 // cleanup all references to this role, ignore errors
2366 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
2367 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
2368 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
2369 $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2370 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2371 $DB->delete_records('role_names', array('roleid'=>$roleid));
2372 $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
60ace1e1 2373
4f0c2d00 2374 // finally delete the role itself
cb8cb8bf 2375 // get this before the name is gone for logging
f33e1ed4 2376 $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
5a4e7398 2377
4f0c2d00 2378 $DB->delete_records('role', array('id'=>$roleid));
5a4e7398 2379
4f0c2d00 2380 add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
8420bee9 2381
4f0c2d00 2382 return true;
8420bee9 2383}
2384
bbbf2d40 2385/**
2386 * Function to write context specific overrides, or default capabilities.
46808d7c 2387 *
cc3edaa4 2388 * @global object
2389 * @global object
46808d7c 2390 * @param string module string name
2391 * @param string capability string name
2392 * @param int contextid context id
2393 * @param int roleid role id
2394 * @param int permission int 1,-1 or -1000 should not be writing if permission is 0
2395 * @return bool
bbbf2d40 2396 */
e7876c1e 2397function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
f33e1ed4 2398 global $USER, $DB;
eef868d1 2399
96986241 2400 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
eef868d1 2401 unassign_capability($capability, $roleid, $contextid);
96986241 2402 return true;
98882637 2403 }
eef868d1 2404
f33e1ed4 2405 $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
e7876c1e 2406
2407 if ($existing and !$overwrite) { // We want to keep whatever is there already
2408 return true;
2409 }
2410
bbbf2d40 2411 $cap = new object;
2412 $cap->contextid = $contextid;
2413 $cap->roleid = $roleid;
2414 $cap->capability = $capability;
2415 $cap->permission = $permission;
2416 $cap->timemodified = time();
9db12da7 2417 $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
e7876c1e 2418
2419 if ($existing) {
2420 $cap->id = $existing->id;
4f0c2d00 2421 $DB->update_record('role_capabilities', $cap);
e7876c1e 2422 } else {
f33e1ed4 2423 $c = $DB->get_record('context', array('id'=>$contextid));
4f0c2d00 2424 $DB->insert_record('role_capabilities', $cap);
e7876c1e 2425 }
4f0c2d00 2426 return true;
bbbf2d40 2427}
2428
bbbf2d40 2429/**
2430 * Unassign a capability from a role.
117bd748 2431 *
cc3edaa4 2432 * @global object
46808d7c 2433 * @param int $roleid the role id
2434 * @param string $capability the name of the capability
2435 * @return boolean success or failure
bbbf2d40 2436 */
2437function unassign_capability($capability, $roleid, $contextid=NULL) {
f33e1ed4 2438 global $DB;
eef868d1 2439
4f0c2d00 2440 if (!empty($contextid)) {
c345bb58 2441 // delete from context rel, if this is the last override in this context
4f0c2d00 2442 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
98882637 2443 } else {
4f0c2d00 2444 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
98882637 2445 }
4f0c2d00 2446 return true;
bbbf2d40 2447}
2448
2449
2450/**
46808d7c 2451 * Get the roles that have a given capability assigned to it
759ac72d 2452 * Get the roles that have a given capability assigned to it. This function
2453 * does not resolve the actual permission of the capability. It just checks
2454 * for assignment only.
cc3edaa4 2455 *
2456 * @global object
2457 * @global object
46808d7c 2458 * @param string $capability - capability name (string)
4f0c2d00 2459 * @param string $permission - optional, the permission defined for this capability
46808d7c 2460 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL
2461 * @param object $contect
2462 * @return mixed array or role objects
bbbf2d40 2463 */
4f0c2d00 2464function get_roles_with_capability($capability, $permission=NULL, $context=NULL) {
f33e1ed4 2465 global $CFG, $DB;
eef868d1 2466
f33e1ed4 2467 $params = array();
5a4e7398 2468
ec7a8b79 2469 if ($context) {
2470 if ($contexts = get_parent_contexts($context)) {
2471 $listofcontexts = '('.implode(',', $contexts).')';
2472 } else {
21c9bace 2473 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
eef868d1 2474 $listofcontexts = '('.$sitecontext->id.')'; // must be site
2475 }
f33e1ed4 2476 $contextstr = "AND (rc.contextid = ? OR rc.contextid IN $listofcontexts)";
2477 $params[] = $context->id;
ec7a8b79 2478 } else {
2479 $contextstr = '';
2480 }
eef868d1 2481
2482 $selectroles = "SELECT r.*
5a4e7398 2483 FROM {role} r,
2484 {role_capabilities} rc
f33e1ed4 2485 WHERE rc.capability = ?
5a4e7398 2486 AND rc.roleid = r.id $contextstr";
bbbf2d40 2487
f33e1ed4 2488 array_unshift($params, $capability);
2489
bbbf2d40 2490 if (isset($permission)) {
f33e1ed4 2491 $selectroles .= " AND rc.permission = ?";
2492 $params[] = $permission;
bbbf2d40 2493 }
f33e1ed4 2494 return $DB->get_records_sql($selectroles, $params);
bbbf2d40 2495}
2496
2497
2498/**
df997f84 2499 * This function makes a role-assignment (a role for a user in a particular context)
117bd748 2500 *
46808d7c 2501 * @param int $roleid the role of the id
2502 * @param int $userid userid
46808d7c 2503 * @param int $contextid id of the context
df997f84
PS
2504 * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
2505 * @prama int $itemid id of enrolment/auth plugin
2506 * @param string $timemodified defaults to current time
2507 * @return int new/existing id of the assignment
bbbf2d40 2508 */
df997f84 2509function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
f33e1ed4 2510 global $USER, $CFG, $DB;
bbbf2d40 2511
df997f84
PS
2512 // first of all detect if somebody is using old style parameters
2513 if ($contextid === 0 or is_numeric($component)) {
2514 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
2515 }
a9e1c058 2516
df997f84 2517 // now validate all parameters
bbbf2d40 2518 if (empty($roleid)) {
df997f84 2519 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
bbbf2d40 2520 }
2521
e8c2189d 2522 if (empty($userid)) {
df997f84 2523 throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
bbbf2d40 2524 }
eef868d1 2525
df997f84
PS
2526 if ($itemid) {
2527 if (strpos($component, '_') === false) {
2528 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
2529 }
2530 } else {
2531 $itemid = 0;
2532 if ($component !== '' and strpos($component, '_') === false) {
2533 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2534 }
7700027f 2535 }
bbbf2d40 2536
df997f84
PS
2537 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
2538 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
a9e1c058 2539 return false;
bbbf2d40 2540 }
2541
df997f84 2542 $context = get_context_instance_by_id($contextid, MUST_EXIST);
a9e1c058 2543
69b0088c 2544 if (!$timemodified) {
c421ad4b 2545 $timemodified = time();
69b0088c 2546 }
7700027f 2547
a9e1c058 2548/// Check for existing entry
df997f84
PS
2549 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
2550
2551 if ($ras) {
2552 // role already assigned - this should not happen
2553 if (count($ras) > 1) {
2554 //very weird - remove all duplicates!
2555 $ra = array_shift($ras);
2556 foreach ($ras as $r) {
2557 $DB->delete_records('role_assignments', array('id'=>$r->id));
2558 }
2559 } else {
2560 $ra = reset($ras);
2561 }
128f0984 2562
df997f84
PS
2563 // actually there is no need to update, reset anything or trigger any event, so just return
2564 return $ra->id;
96608a55 2565 }
c421ad4b 2566
df997f84
PS
2567 // Create a new entry
2568 $ra = new object();
2569 $ra->roleid = $roleid;
2570 $ra->contextid = $context->id;
2571 $ra->userid = $userid;
2572 $ra->component = $component;
2573 $ra->itemid = $itemid;
2574 $ra->timemodified = $timemodified;
2575 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
eef868d1 2576
df997f84
PS
2577 $ra->id = $DB->insert_record('role_assignments', $ra);
2578
2579 // mark context as dirty - again expensive, but needed
2580 mark_context_dirty($context->path);
2581
2582 if (!empty($USER->id) && $USER->id == $userid) {
2583 // If the user is the current user, then do full reload of capabilities too.
2584 load_all_capabilities();
4e5f3064 2585 }
6eb4f823 2586
96608a55 2587 events_trigger('role_assigned', $ra);
2588
386c151e 2589 return $ra->id;
bbbf2d40 2590}
2591
bbbf2d40 2592/**
df997f84 2593 * Removes one role assignment
cc3edaa4 2594 *
df997f84
PS
2595 * @param int $roleid
2596 * @param int $userid
2597 * @param int $contextid
2598 * @param string $component
2599 * @param int $itemid
2600 * @return void
bbbf2d40 2601 */
df997f84 2602function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
5a4e7398 2603 global $USER, $CFG, $DB;
5a4e7398 2604
df997f84
PS
2605 // first make sure the params make sense
2606 if ($roleid == 0 or $userid == 0 or $contextid == 0) {
2607 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
6bc1e5d5 2608 }
d74067e8 2609
df997f84
PS
2610 if ($itemid) {
2611 if (strpos($component, '_') === false) {
2612 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
2613 }
2614 } else {
2615 $itemid = 0;
2616 if ($component !== '' and strpos($component, '_') === false) {
2617 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
4f0c2d00
PS
2618 }
2619 }
2620
df997f84 2621 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
4f0c2d00
PS
2622}
2623
2624/**
df997f84
PS
2625 * Removes multiple role assignments, parameters may contain:
2626 * 'roleid', 'userid', 'contextid', 'component', 'enrolid'.
4f0c2d00 2627 *
df997f84
PS
2628 * @param array $params role assignment parameters
2629 * @param bool $subcontexts unassign in subcontexts too
2630 * @param bool $includmanual include manual role assignments too
2631 * @return void
4f0c2d00 2632 */
df997f84
PS
2633function role_unassign_all(array $params, $subcontexts = false, $includemanual=false) {
2634 global $USER, $CFG, $DB;
4f0c2d00 2635
df997f84
PS
2636 if (!$params) {
2637 throw new coding_exception('Missing parameters in role_unsassign_all() call');
4f0c2d00
PS
2638 }
2639
df997f84
PS
2640 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
2641 foreach ($params as $key=>$value) {
2642 if (!in_array($key, $allowed)) {
2643 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
2644 }
2645 }
4f0c2d00 2646
df997f84
PS
2647 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
2648 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
2649 }
4f0c2d00 2650
df997f84
PS
2651 if ($includemanual) {
2652 if (!isset($params['component']) or $params['component'] === '') {
2653 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
4f0c2d00 2654 }
df997f84 2655 }
4f0c2d00 2656
df997f84
PS
2657 if ($subcontexts) {
2658 if (empty($params['contextid'])) {
2659 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
2660 }
2661 }
4f0c2d00 2662
df997f84
PS
2663 $ras = $DB->get_records('role_assignments', $params);
2664 foreach($ras as $ra) {
2665 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2666 if ($context = get_context_instance_by_id($ra->contextid)) {
2667 // this is a bit expensive but necessary
2668 mark_context_dirty($context->path);
2669 /// If the user is the current user, then do full reload of capabilities too.
2670 if (!empty($USER->id) && $USER->id == $ra->userid) {
2671 load_all_capabilities();
2672 }
2673 }
2674 events_trigger('role_unassigned', $ra);
2675 }
2676 unset($ras);
4f0c2d00 2677
df997f84
PS
2678 // process subcontexts
2679 if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) {
2680 $contexts = get_child_contexts($context);
2681 $mparams = $params;
2682 foreach($contexts as $context) {
2683 $mparams['contextid'] = $context->id;
2684 $ras = $DB->get_records('role_assignments', $mparams);
2685 foreach($ras as $ra) {
2686 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2687 // this is a bit expensive but necessary
2688 mark_context_dirty($context->path);
2689 /// If the user is the current user, then do full reload of capabilities too.
2690 if (!empty($USER->id) && $USER->id == $ra->userid) {
2691 load_all_capabilities();
2692 }
2693 events_trigger('role_unassigned', $ra);
2694 }
2695 }
4f0c2d00
PS
2696 }
2697
df997f84
PS
2698 // do this once more for all manual role assignments
2699 if ($includemanual) {
2700 $params['component'] = '';
2701 role_unassign_all($params, $subcontexts, false);
2702 }
4f0c2d00
PS
2703}
2704
df997f84 2705
4f0c2d00
PS
2706/**
2707 * Determines if a user is currently logged in
2708 *
2709 * @return bool
2710 */
2711function isloggedin() {
2712 global $USER;
2713
2714 return (!empty($USER->id));
2715}
2716
2717/**
2718 * Determines if a user is logged in as real guest user with username 'guest'.
2719 *
df997f84 2720 * @param int|object $user mixed user object or id, $USER if not specified
4f0c2d00
PS
2721 * @return bool true if user is the real guest user, false if not logged in or other user
2722 */
2723function isguestuser($user = NULL) {
2724 global $USER, $DB, $CFG;
2725
2726 // make sure we have the user id cached in config table, because we are going to use it a lot
2727 if (empty($CFG->siteguest)) {
2728 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
2729 // guest does not exist yet, weird
2730 return false;
2731 }
2732 set_config('siteguest', $guestid);
2733 }
2734 if ($user === NULL) {
2735 $user = $USER;
2736 }
2737
2738 if ($user === NULL) {
2739 // happens when setting the $USER
2740 return false;
2741
2742 } else if (is_numeric($user)) {
2743 return ($CFG->siteguest == $user);
2744
2745 } else if (is_object($user)) {
2746 if (empty($user->id)) {
2747 return false; // not logged in means is not be guest
2748 } else {
2749 return ($CFG->siteguest == $user->id);
2750 }
2751
2752 } else {
2753 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
2754 }
2755}
2756
2757/**
2758 * Does user have a (temporary or real) guest access to course?
2759 *
2760 * @param object $context
2761 * @param object|int $user
2762 * @return bool
2763 */
2764function is_guest($context, $user = NULL) {
2765 // first find the course context
2766 $coursecontext = get_course_context($context);
2767
2768 // make sure there is a real user specified
2769 if ($user === NULL) {
2770 $userid = !empty($USER->id) ? $USER->id : 0;
2771 } else {
2772 $userid = !empty($user->id) ? $user->id : $user;
2773 }
2774
2775 if (isguestuser($userid)) {
2776 // can not inspect or be enrolled
2777 return true;
2778 }
2779
2780 if (has_capability('moodle/course:view', $coursecontext, $user)) {
2781 // viewing users appear out of nowhere, they are neither guests nor participants
2782 return false;
2783 }
2784
df997f84
PS
2785 // consider only real active enrolments here
2786 if (is_enrolled($coursecontext, $user, '', true)) {
4f0c2d00
PS
2787 return false;
2788 }
2789
2790 return true;
2791}
2792
2793
2794/**
2418d71e 2795 * Returns true if the user has moodle/course:view capability in the course,
4f0c2d00
PS
2796 * this is intended for admins, managers (aka small admins), inspectors, etc.
2797 *
2798 * @param object $context
2799 * @param int|object $user, if NULL $USER is used
2800 * @param string $withcapability extra capability name
2801 * @return bool
2802 */
2803function is_viewing($context, $user = NULL, $withcapability = '') {
2804 global $USER;
2805
2806 // first find the course context
2807 $coursecontext = get_course_context($context);
2808
2809 if (isguestuser($user)) {
2810 // can not inspect
83bbafaa 2811 return false;
4f0c2d00
PS
2812 }
2813
2814 if (!has_capability('moodle/course:view', $coursecontext, $user)) {
2815 // admins are allowed to inspect courses
2816 return false;
2817 }
2818
2819 if ($withcapability and !has_capability($withcapability, $context, $user)) {
2820 // site admins always have the capability, but the enrolment above blocks
2821 return false;
2822 }
2823
2824 return true;
2825}
2826
2827/**
2828 * Returns true if user is enrolled (is participating) in course
2829 * this is intended for students and teachers.
2830 *
2831 * @param object $context
4f65e0fb 2832 * @param int|object $user, if NULL $USER is used, otherwise user object or id expected
4f0c2d00 2833 * @param string $withcapability extra capability name
df997f84 2834 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
4f0c2d00
PS
2835 * @return bool
2836 */
df997f84
PS
2837function is_enrolled($context, $user = NULL, $withcapability = '', $onlyactive = false) {
2838 global $USER, $DB;
4f0c2d00
PS
2839
2840 // first find the course context
2841 $coursecontext = get_course_context($context);
2842
2843 // make sure there is a real user specified
2844 if ($user === NULL) {
2845 $userid = !empty($USER->id) ? $USER->id : 0;
2846 } else {
2847 $userid = !empty($user->id) ? $user->id : $user;
2848 }
2849
2850 if (empty($userid)) {
2851 // not-logged-in!
2852 return false;
2853 } else if (isguestuser($userid)) {
2854 // guest account can not be enrolled anywhere
2855 return false;
2856 }
2857
df997f84
PS
2858 if ($coursecontext->instanceid == SITEID) {
2859 // everybody participates on frontpage
2860 } else {
2861 if ($onlyactive) {
2862 $sql = "SELECT ue.*
2863 FROM {user_enrolments} ue
2864 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2865 JOIN {user} u ON u.id = ue.userid
2866 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
2867 $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2868 // this result should be very small, better not do the complex time checks in sql for now ;-)
2869 $enrolments = $DB->get_records_sql($sql, $params);
2870 $now = time();
2871 // make sure the enrol period is ok
2872 $result = false;
2873 foreach ($enrolments as $e) {
2874 if ($e->timestart > $now) {
2875 continue;
2876 }
2877 if ($e->timeend and $e->timeend < $now) {
2878 continue;
2879 }
2880 $result = true;
2881 break;
2882 }
2883 if (!$result) {
2884 return false;
2885 }
2886
2887 } else {
2888 // any enrolment is good for us here, even outdated, disabled or inactive
2889 $sql = "SELECT 'x'
2890 FROM {user_enrolments} ue
2891 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2892 JOIN {user} u ON u.id = ue.userid
2893 WHERE ue.userid = :userid AND u.deleted = 0";
2894 $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2895 if (!$DB->record_exists_sql($sql, $params)) {
2896 return false;
2897 }
2898 }
4f0c2d00
PS
2899 }
2900
2901 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2902 return false;
2903 }
2904
2905 return true;
2906}
2907
2908/**
2909 * Returns array with sql code and parameters returning all ids
2910 * of users enrolled into course.
df997f84
PS
2911 *
2912 * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2913 *
4f0c2d00
PS
2914 * @param object $context
2915 * @param string $withcapability
2916 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
df997f84 2917 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
4f0c2d00
PS
2918 * @return array list($sql, $params)
2919 */
df997f84 2920function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) {
b3df1764 2921 global $DB, $CFG;
4f0c2d00 2922
df997f84
PS
2923 // use unique prefix just in case somebody makes some SQL magic with the result
2924 static $i = 0;
2925 $i++;
2926 $prefix = 'eu'.$i.'_';
4f0c2d00
PS
2927
2928 // first find the course context
df997f84 2929 $coursecontext = get_course_context($context);
4f0c2d00 2930
df997f84 2931 $isfrontpage = ($coursecontext->instanceid == SITEID);
4f0c2d00 2932
df997f84
PS
2933 $joins = array();
2934 $wheres = array();
2935 $params = array();
4f0c2d00
PS
2936
2937 list($contextids, $contextpaths) = get_context_info_list($context);
4f0c2d00
PS
2938
2939 // get all relevant capability info for all roles
2940 if ($withcapability) {
df997f84
PS
2941 list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx00');
2942 $cparams['cap'] = $withcapability;
2943
2944 $defs = array();
2945 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
2946 FROM {role_capabilities} rc
2947 JOIN {context} ctx on rc.contextid = ctx.id
2948 WHERE rc.contextid $incontexts AND rc.capability = :cap";
2949 $rcs = $DB->get_records_sql($sql, $cparams);
2950 foreach ($rcs as $rc) {
2951 $defs[$rc->path][$rc->roleid] = $rc->permission;
4f0c2d00 2952 }
4f0c2d00 2953
df997f84
PS
2954 $access = array();
2955 if (!empty($defs)) {
2956 foreach ($contextpaths as $path) {
2957 if (empty($defs[$path])) {
4f0c2d00
PS
2958 continue;
2959 }
df997f84
PS
2960 foreach($defs[$path] as $roleid => $perm) {
2961 if ($perm == CAP_PROHIBIT) {
2962 $access[$roleid] = CAP_PROHIBIT;
2963 continue;
2964 }
2965 if (!isset($access[$roleid])) {
2966 $access[$roleid] = (int)$perm;
2967 }
4f0c2d00
PS
2968 }
2969 }
2970 }
4f0c2d00 2971
df997f84 2972 unset($defs);
4f0c2d00 2973
df997f84
PS
2974 // make lists of roles that are needed and prohibited
2975 $needed = array(); // one of these is enough
2976 $prohibited = array(); // must not have any of these
2977 if ($withcapability) {
2978 foreach ($access as $roleid => $perm) {
2979 if ($perm == CAP_PROHIBIT) {
2980 unset($needed[$roleid]);
2981 $prohibited[$roleid] = true;
2982 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
2983 $needed[$roleid] = true;
2984 }
4f0c2d00
PS
2985 }
2986 }
4f0c2d00 2987
df997f84
PS
2988 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : NULL;
2989 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : NULL;
2515adf9 2990
df997f84 2991 $nobody = false;
2515adf9 2992
4f0c2d00
PS
2993 if ($isfrontpage) {
2994 if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
2995 $nobody = true;
2996 } else if (!empty($neded[$defaultuserroleid]) or !empty($neded[$defaultfrontpageroleid])) {
2997 // everybody not having prohibit has the capability
2998 $needed = array();
2999 } else if (empty($needed)) {
3000 $nobody = true;
3001 }
3002 } else {
3003 if (!empty($prohibited[$defaultuserroleid])) {
3004 $nobody = true;
3005 } else if (!empty($neded[$defaultuserroleid])) {
3006 // everybody not having prohibit has the capability
3007 $needed = array();
3008 } else if (empty($needed)) {
3009 $nobody = true;
0f161e1f 3010 }
d74067e8 3011 }
4e5f3064 3012
df997f84
PS
3013 if ($nobody) {
3014 // nobody can match so return some SQL that does not return any results
3015 $wheres[] = "1 = 2";
bbbf2d40 3016
df997f84 3017 } else {
b963384f 3018
df997f84
PS
3019 if ($needed) {
3020 $ctxids = implode(',', $contextids);
3021 $roleids = implode(',', array_keys($needed));
3022 $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))";
3023 }
b963384f 3024
df997f84
PS
3025 if ($prohibited) {
3026 $ctxids = implode(',', $contextids);
3027 $roleids = implode(',', array_keys($prohibited));
3028 $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))";
a4c0961c 3029 $wheres[] = "{$prefix}ra4.id IS NULL";
df997f84 3030 }
c4381ef5 3031
df997f84
PS
3032 if ($groupid) {
3033 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.id = :{$prefix}gmid)";
3034 $params["{$prefix}gmid"] = $groupid;
3035 }
3036 }
c4381ef5 3037
4f0c2d00 3038 }
eef868d1 3039
b3df1764
PS
3040 $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
3041 $params["{$prefix}guestid"] = $CFG->siteguest;
df997f84
PS
3042
3043 if ($isfrontpage) {
3044 // all users are "enrolled" on the frontpage
3045 } else {
3046 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
3047 $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
3048 $params[$prefix.'courseid'] = $coursecontext->instanceid;
3049
3050 if ($onlyactive) {
3051 $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
3052 $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
3053 $now = round(time(), -2); // rounding helps caching in DB
3054 $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
3055 $prefix.'active'=>ENROL_USER_ACTIVE,
3056 $prefix.'now1'=>$now, $prefix.'now2'=>$now));
3057 }
4f0c2d00 3058 }
a9d4ea78 3059
4f0c2d00
PS
3060 $joins = implode("\n", $joins);
3061 $wheres = "WHERE ".implode(" AND ", $wheres);
eef868d1 3062
df997f84
PS
3063 $sql = "SELECT DISTINCT {$prefix}u.id
3064 FROM {user} {$prefix}u
4f0c2d00
PS
3065 $joins
3066 $wheres";
b963384f 3067
4f0c2d00
PS
3068 return array($sql, $params);
3069}
3070
3071/**
3072 * Returns list of users enrolled into course.
3073 * @param object $context
3074 * @param string $withcapability
3075 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3076 * @param string $userfields requested user record fields
3077 * @param string $orderby
3078 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
3079 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
3080 * @return array of user records
3081 */
3082function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
3083 global $DB;
3084
3085 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3086 $sql = "SELECT $userfields
3087 FROM {user} u
3088 JOIN ($esql) je ON je.id = u.id
3089 WHERE u.deleted = 0";
3090
3091 if ($orderby) {
3092 $sql = "$sql ORDER BY $orderby";
3093 } else {
3094 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
b963384f 3095 }
3096
4f0c2d00 3097 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
b963384f 3098}
3099
dcc779cc
MD
3100/**
3101 * Counts list of users enrolled into course (as per above function)
3102 * @param object $context
3103 * @param string $withcapability
3104 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3105 * @return array of user records
3106 */
3107function count_enrolled_users($context, $withcapability = '', $groupid = 0) {
3108 global $DB;
3109
3110 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3111 $sql = "SELECT count(u.id)
3112 FROM {user} u
3113 JOIN ($esql) je ON je.id = u.id
3114 WHERE u.deleted = 0";
3115
3116 return $DB->count_records_sql($sql, $params);
3117}
3118
3119
bbbf2d40 3120/**
46808d7c 3121 * Loads the capability definitions for the component (from file).
3122 *
bbbf2d40 3123 * Loads the capability definitions for the component (from file). If no
3124 * capabilities are defined for the component, we simply return an empty array.
46808d7c 3125 *
cc3edaa4 3126 * @global object
17da2e6f 3127 * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
46808d7c 3128 * @return array array of capabilities
bbbf2d40 3129 */
3130function load_capability_def($component) {
17da2e6f 3131 $defpath = get_component_directory($component).'/db/access.php';
5ca3c838 3132
bbbf2d40 3133 $capabilities = array();
bbbf2d40 3134 if (file_exists($defpath)) {
dc268b2f 3135 require($defpath);
0ac940b3 3136 if (!empty(${$component.'_capabilities'})) {
4f0c2d00 3137 // BC capability array name
0ac940b3 3138 // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
4f0c2d00 3139 debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files');
0ac940b3
PS
3140 $capabilities = ${$component.'_capabilities'};
3141 }
bbbf2d40 3142 }
17da2e6f 3143
bbbf2d40 3144 return $capabilities;
3145}
3146
3147
3148/**
46808d7c 3149 * Gets the capabilities that have been cached in the database for this component.
17da2e6f 3150 * @param string $component - examples: 'moodle', 'mod_forum'
46808d7c 3151 * @return array array of capabilities
bbbf2d40 3152 */
3153function get_cached_capabilities($component='moodle') {
5a4e7398 3154 global $DB;
17da2e6f 3155 return $DB->get_records('capabilities', array('component'=>$component));