MDL-24339 fix for old mysql invalid NULLs in resource options field
[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) {
2fb34345 734 global $DB, $CFG;
bbdb7070 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)";
b2cd6570 742 return $DB->record_exists_sql($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
f2b7f454 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) {
7875b1fd 1689 return get_system_context();
4881f2d3 1690 }
c421ad4b 1691
365a5941 1692 $context = new stdClass();
4881f2d3 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)) {
365a5941 1846 $ACCESSLIB_PRIVATE->systemcontext = new stdClass();
d867e696 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) {
365a5941 1863 $context = new stdClass();
8ba412da 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
582bae08 1898 * @param bool $deleterecord false means keep record for now
196f1a25 1899 * @return bool returns true or throws an exception
9991d157 1900 */
582bae08 1901function delete_context($contextlevel, $instanceid, $deleterecord = true) {
8432f5e6 1902 global $DB, $ACCESSLIB_PRIVATE, $CFG;
b51ece5b 1903
1904 // do not use get_context_instance(), because the related object might not exist,
1905 // or the context does not exist yet and it would be created now
5a4e7398 1906 if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
582bae08
PS
1907 // delete these first because they might fetch the context and try to recreate it!
1908 blocks_delete_all_for_context($context->id);
1909 filter_delete_all_for_context($context->id);
2ac56258 1910
582bae08
PS
1911 require_once($CFG->dirroot . '/comment/lib.php');
1912 comment::delete_comments(array('contextid'=>$context->id));
b51ece5b 1913
2ac56258
AD
1914 require_once($CFG->dirroot.'/rating/lib.php');
1915 $delopt = new stdclass();
1916 $delopt->contextid = $context->id;
1917 $rm = new rating_manager();
1918 $rm->delete_ratings($delopt);
1919
a05bcfba
PS
1920 // delete all files attached to this context
1921 $fs = get_file_storage();
1922 $fs->delete_area_files($context->id);
1923
582bae08
PS
1924 // now delete stuff from role related tables, role_unassign_all
1925 // and unenrol should be called earlier to do proper cleanup
1926 $DB->delete_records('role_assignments', array('contextid'=>$context->id));
1927 $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
1928 $DB->delete_records('role_names', array('contextid'=>$context->id));
1929
1930 // and finally it is time to delete the context record if requested
1931 if ($deleterecord) {
1932 $DB->delete_records('context', array('id'=>$context->id));
1933 // purge static context cache if entry present
1934 unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]);
1935 unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]);
1936 }
1937
b51ece5b 1938 // do not mark dirty contexts if parents unknown
1939 if (!is_null($context->path) and $context->depth > 0) {
1940 mark_context_dirty($context->path);
1941 }
9991d157 1942 }
196f1a25
PS
1943
1944 return true;
9991d157 1945}
1946
9a81a606 1947/**
1948 * Precreates all contexts including all parents
cc3edaa4 1949 *
1950 * @global object
46808d7c 1951 * @param int $contextlevel empty means all
9a81a606 1952 * @param bool $buildpaths update paths and depths
1953 * @return void
1954 */
4f0c2d00 1955function create_contexts($contextlevel=NULL, $buildpaths=true) {
5a4e7398 1956 global $DB;
9a81a606 1957
1958 //make sure system context exists
1959 $syscontext = get_system_context(false);
1960
5c8e6cb1 1961 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
1962 or $contextlevel == CONTEXT_COURSE
1963 or $contextlevel == CONTEXT_MODULE
1964 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1965 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1966 SELECT ".CONTEXT_COURSECAT.", cc.id
5a4e7398 1967 FROM {course}_categories cc
9a81a606 1968 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1969 FROM {context} cx
9a81a606 1970 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
5a4e7398 1971 $DB->execute($sql);
9a81a606 1972
1973 }
1974
5c8e6cb1 1975 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
1976 or $contextlevel == CONTEXT_MODULE
1977 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1978 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1979 SELECT ".CONTEXT_COURSE.", c.id
5a4e7398 1980 FROM {course} c
9a81a606 1981 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1982 FROM {context} cx
9a81a606 1983 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
5a4e7398 1984 $DB->execute($sql);
9a81a606 1985
1986 }
1987
e92c286c 1988 if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
1989 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 1990 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 1991 SELECT ".CONTEXT_MODULE.", cm.id
5a4e7398 1992 FROM {course}_modules cm
9a81a606 1993 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 1994 FROM {context} cx
9a81a606 1995 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
5a4e7398 1996 $DB->execute($sql);
9a81a606 1997 }
1998
e92c286c 1999 if (empty($contextlevel) or $contextlevel == CONTEXT_USER
2000 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 2001 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 2002 SELECT ".CONTEXT_USER.", u.id
5a4e7398 2003 FROM {user} u
9a81a606 2004 WHERE u.deleted=0
2005 AND NOT EXISTS (SELECT 'x'
5a4e7398 2006 FROM {context} cx
9a81a606 2007 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
5a4e7398 2008 $DB->execute($sql);
9a81a606 2009
2010 }
2011
e92c286c 2012 if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
2013 $sql = "INSERT INTO {context} (contextlevel, instanceid)
2014 SELECT ".CONTEXT_BLOCK.", bi.id
2015 FROM {block_instances} bi
2016 WHERE NOT EXISTS (SELECT 'x'
2017 FROM {context} cx
2018 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2019 $DB->execute($sql);
2020 }
2021
9a81a606 2022 if ($buildpaths) {
5a4e7398 2023 build_context_path(false);
9a81a606 2024 }
2025}
2026
17b0efae 2027/**
2028 * Remove stale context records
2029 *
cc3edaa4 2030 * @global object
17b0efae 2031 * @return bool
2032 */
2033function cleanup_contexts() {
5a4e7398 2034 global $DB;
17b0efae 2035
70dd126e 2036 $sql = " SELECT c.contextlevel,
17b0efae 2037 c.instanceid AS instanceid
5a4e7398 2038 FROM {context} c
2039 LEFT OUTER JOIN {course}_categories t
2040 ON c.instanceid = t.id
2041 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
17b0efae 2042 UNION
70dd126e 2043 SELECT c.contextlevel,
2044 c.instanceid
5a4e7398 2045 FROM {context} c
2046 LEFT OUTER JOIN {course} t
2047 ON c.instanceid = t.id
2048 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
17b0efae 2049 UNION
70dd126e 2050 SELECT c.contextlevel,
2051 c.instanceid
5a4e7398 2052 FROM {context} c
2053 LEFT OUTER JOIN {course}_modules t
2054 ON c.instanceid = t.id
2055 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
17b0efae 2056 UNION
70dd126e 2057 SELECT c.contextlevel,
2058 c.instanceid
5a4e7398 2059 FROM {context} c
2060 LEFT OUTER JOIN {user} t
2061 ON c.instanceid = t.id
2062 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
17b0efae 2063 UNION
70dd126e 2064 SELECT c.contextlevel,
2065 c.instanceid
5a4e7398 2066 FROM {context} c
f474a4e5 2067 LEFT OUTER JOIN {block_instances} t
2068 ON c.instanceid = t.id
5a4e7398 2069 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
17b0efae 2070 ";
d5a8d9aa
PS
2071
2072 // transactions used only for performance reasons here
2073 $transaction = $DB->start_delegated_transaction();
2074
5a4e7398 2075 if ($rs = $DB->get_recordset_sql($sql)) {
5a4e7398 2076 foreach ($rs as $ctx) {
d5a8d9aa 2077 delete_context($ctx->contextlevel, $ctx->instanceid);
17b0efae 2078 }
5a4e7398 2079 $rs->close();
17b0efae 2080 }
d5a8d9aa
PS
2081
2082 $transaction->allow_commit();
17b0efae 2083 return true;
2084}
2085
00653161 2086/**
e92c286c 2087 * Preloads all contexts relating to a course: course, modules. Block contexts
2088 * are no longer loaded here. The contexts for all the blocks on the current
2089 * page are now efficiently loaded by {@link block_manager::load_blocks()}.
00653161 2090 *
2091 * @param int $courseid Course ID
d993468d 2092 * @return void
00653161 2093 */
2094function preload_course_contexts($courseid) {
d867e696 2095 global $DB, $ACCESSLIB_PRIVATE;
00653161 2096
2097 // Users can call this multiple times without doing any harm
d867e696 2098 global $ACCESSLIB_PRIVATE;
2099 if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
00653161 2100 return;
2101 }
2102
d993468d 2103 $params = array($courseid, $courseid, $courseid);
2104 $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2105 FROM {course_modules} cm
2106 JOIN {context} x ON x.instanceid=cm.id
2107 WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2108
d993468d 2109 UNION ALL
2110
2111 SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2112 FROM {context} x
2113 WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2114
2115 $rs = $DB->get_recordset_sql($sql, $params);
00653161 2116 foreach($rs as $context) {
d867e696 2117 cache_context($context);
00653161 2118 }
2119 $rs->close();
d867e696 2120 $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
00653161 2121}
2122
bbbf2d40 2123/**
2124 * Get the context instance as an object. This function will create the
2125 * context instance if it does not exist yet.
46808d7c 2126 *
2127 * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2128 *
e765b5d3 2129 * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2130 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
46808d7c 2131 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
4f0c2d00
PS
2132 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2133 * MUST_EXIST means throw exception if no record or multiple records found
e765b5d3 2134 * @return object The context object.
bbbf2d40 2135 */
4f0c2d00 2136function get_context_instance($contextlevel, $instance=0, $strictness=IGNORE_MISSING) {
e5605780 2137
d867e696 2138 global $DB, $ACCESSLIB_PRIVATE;
8ead7b59 2139 static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
d9a35e12 2140
7d0c81b3 2141/// System context has special cache
8ba412da 2142 if ($contextlevel == CONTEXT_SYSTEM) {
7d0c81b3 2143 return get_system_context();
8ba412da 2144 }
2145
a36a3a3f 2146/// check allowed context levels
2147 if (!in_array($contextlevel, $allowed_contexts)) {
7bfa3101 2148 // fatal error, code must be fixed - probably typo or switched parameters
e49ef64a 2149 print_error('invalidcourselevel');
a36a3a3f 2150 }
2151
65bcf17b 2152 if (!is_array($instance)) {
2153 /// Check the cache
d867e696 2154 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2155 return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2156 }
2157
2158 /// Get it from the database, or create it
5a4e7398 2159 if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
4f0c2d00 2160 $context = create_context($contextlevel, $instance, $strictness);
65bcf17b 2161 }
2162
2163 /// Only add to cache if context isn't empty.
2164 if (!empty($context)) {
d867e696 2165 cache_context($context);
65bcf17b 2166 }
2167
2168 return $context;
e5605780 2169 }
2170
65bcf17b 2171
2172/// ok, somebody wants to load several contexts to save some db queries ;-)
2173 $instances = $instance;
2174 $result = array();
2175
2176 foreach ($instances as $key=>$instance) {
2177 /// Check the cache first
d867e696 2178 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2179 $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2180 unset($instances[$key]);
2181 continue;
2182 }
e5605780 2183 }
2184
65bcf17b 2185 if ($instances) {
5a4e7398 2186 list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2187 array_unshift($params, $contextlevel);
2188 $sql = "SELECT instanceid, id, contextlevel, path, depth
2189 FROM {context}
2190 WHERE contextlevel=? AND instanceid $instanceids";
2191
2192 if (!$contexts = $DB->get_records_sql($sql, $params)) {
65bcf17b 2193 $contexts = array();
2194 }
2195
2196 foreach ($instances as $instance) {
2197 if (isset($contexts[$instance])) {
2198 $context = $contexts[$instance];
2199 } else {
2200 $context = create_context($contextlevel, $instance);
2201 }
2202
2203 if (!empty($context)) {
d867e696 2204 cache_context($context);
65bcf17b 2205 }
2206
2207 $result[$instance] = $context;
2208 }
ccfc5ecc 2209 }
0468976c 2210
65bcf17b 2211 return $result;
bbbf2d40 2212}
2213
cee0901c 2214
340ea4e8 2215/**
e765b5d3 2216 * Get a context instance as an object, from a given context id.
cc3edaa4 2217 *
65bcf17b 2218 * @param mixed $id a context id or array of ids.
01a2ce80
PS
2219 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2220 * MUST_EXIST means throw exception if no record or multiple records found
46808d7c 2221 * @return mixed object, array of the context object, or false.
340ea4e8 2222 */
01a2ce80 2223function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) {
d867e696 2224 global $DB, $ACCESSLIB_PRIVATE;
d9a35e12 2225
7d0c81b3 2226 if ($id == SYSCONTEXTID) {
2227 return get_system_context();
2228 }
2229
d867e696 2230 if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) { // Already cached
2231 return $ACCESSLIB_PRIVATE->contextsbyid[$id];
340ea4e8 2232 }
2233
01a2ce80 2234 if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
d867e696 2235 cache_context($context);
340ea4e8 2236 return $context;
2237 }
2238
2239 return false;
2240}
2241
bbbf2d40 2242
8737be58 2243/**
2244 * Get the local override (if any) for a given capability in a role in a context
cc3edaa4 2245 *
2246 * @global object
46808d7c 2247 * @param int $roleid
2248 * @param int $contextid
2249 * @param string $capability
8737be58 2250 */
2251function get_local_override($roleid, $contextid, $capability) {
5a4e7398 2252 global $DB;
2253 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
8737be58 2254}
2255
01a2ce80
PS
2256/**
2257 * Returns context instance plus related course and cm instances
2258 * @param int $contextid
2259 * @return array of ($context, $course, $cm)
2260 */
2261function get_context_info_array($contextid) {
2262 global $DB;
2263
2264 $context = get_context_instance_by_id($contextid, MUST_EXIST);
4f0c2d00
PS
2265 $course = NULL;
2266 $cm = NULL;
01a2ce80
PS
2267
2268 if ($context->contextlevel == CONTEXT_COURSE) {
2269 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2270
2271 } else if ($context->contextlevel == CONTEXT_MODULE) {
2272 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2273 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2274
2275 } else if ($context->contextlevel == CONTEXT_BLOCK) {
2276 $parentcontexts = get_parent_contexts($context, false);
2277 $parent = reset($parentcontexts);
2278 $parent = get_context_instance_by_id($parent);
2279
2280 if ($parent->contextlevel == CONTEXT_COURSE) {
2281 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2282 } else if ($parent->contextlevel == CONTEXT_MODULE) {
2283 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2284 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2285 }
2286 }
2287
2288 return array($context, $course, $cm);
2289}
8737be58 2290
35716b86
PS
2291/**
2292 * Returns current course id or null if outside of course based on context parameter.
2293 * @param object $context
2294 * @return int|bool related course id or false
2295 */
2296function get_courseid_from_context($context) {
2297 if ($context->contextlevel == CONTEXT_COURSE) {
2298 return $context->instanceid;
2299 }
2300
2301 if ($context->contextlevel < CONTEXT_COURSE) {
2302 return false;
2303 }
2304
2305 if ($context->contextlevel == CONTEXT_MODULE) {
2306 $parentcontexts = get_parent_contexts($context, false);
2307 $parent = reset($parentcontexts);
2308 $parent = get_context_instance_by_id($parent);
2309 return $parent->instanceid;
2310 }
2311
2312 if ($context->contextlevel == CONTEXT_BLOCK) {
2313 $parentcontexts = get_parent_contexts($context, false);
2314 $parent = reset($parentcontexts);
2315 return get_courseid_from_context($parent);
2316 }
2317
2318 return false;
2319}
2320
bbbf2d40 2321
46808d7c 2322//////////////////////////////////////
2323// DB TABLE RELATED FUNCTIONS //
2324//////////////////////////////////////
bbbf2d40 2325
cee0901c 2326/**
bbbf2d40 2327 * function that creates a role
cc3edaa4 2328 *
2329 * @global object
46808d7c 2330 * @param string $name role name
2331 * @param string $shortname role short name
2332 * @param string $description role description
4f0c2d00 2333 * @param string $archetype
46808d7c 2334 * @return mixed id or dml_exception
bbbf2d40 2335 */
4f0c2d00 2336function create_role($name, $shortname, $description, $archetype='') {
f33e1ed4 2337 global $DB;
eef868d1 2338
4f0c2d00
PS
2339 if (strpos($archetype, 'moodle/legacy:') !== false) {
2340 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
2341 }
2342
2343 // verify role archetype actually exists
2344 $archetypes = get_role_archetypes();
2345 if (empty($archetypes[$archetype])) {
2346 $archetype = '';
2347 }
2348
bbdb7070 2349 // Get the system context.
19a4a32e 2350 $context = get_context_instance(CONTEXT_SYSTEM);
31f26796 2351
bbdb7070 2352 // Insert the role record.
365a5941 2353 $role = new stdClass();
ac173d3e 2354 $role->name = $name;
2355 $role->shortname = $shortname;
98882637 2356 $role->description = $description;
4f0c2d00 2357 $role->archetype = $archetype;
eef868d1 2358
8420bee9 2359 //find free sortorder number
bbdb7070 2360 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
716dd163 2361 if (empty($role->sortorder)) {
2362 $role->sortorder = 1;
2363 }
bbdb7070 2364 $id = $DB->insert_record('role', $role);
b5959f30 2365
bbdb7070 2366 return $id;
bbbf2d40 2367}
2368
8420bee9 2369/**
46808d7c 2370 * Function that deletes a role and cleanups up after it
cc3edaa4 2371 *
46808d7c 2372 * @param int $roleid id of role to delete
4f65e0fb 2373 * @return bool always true
8420bee9 2374 */
2375function delete_role($roleid) {
f33e1ed4 2376 global $CFG, $DB;
c421ad4b 2377
4f0c2d00 2378 // first unssign all users
df997f84 2379 role_unassign_all(array('roleid'=>$roleid));
c421ad4b 2380
4f0c2d00
PS
2381 // cleanup all references to this role, ignore errors
2382 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
2383 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
2384 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
2385 $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2386 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2387 $DB->delete_records('role_names', array('roleid'=>$roleid));
2388 $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
60ace1e1 2389
4f0c2d00 2390 // finally delete the role itself
cb8cb8bf 2391 // get this before the name is gone for logging
f33e1ed4 2392 $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
5a4e7398 2393
4f0c2d00 2394 $DB->delete_records('role', array('id'=>$roleid));
5a4e7398 2395
4f0c2d00 2396 add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
8420bee9 2397
4f0c2d00 2398 return true;
8420bee9 2399}
2400
bbbf2d40 2401/**
2402 * Function to write context specific overrides, or default capabilities.
46808d7c 2403 *
cc3edaa4 2404 * @global object
2405 * @global object
46808d7c 2406 * @param string module string name
2407 * @param string capability string name
2408 * @param int contextid context id
2409 * @param int roleid role id
2410 * @param int permission int 1,-1 or -1000 should not be writing if permission is 0
2411 * @return bool
bbbf2d40 2412 */
e7876c1e 2413function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
f33e1ed4 2414 global $USER, $DB;
eef868d1 2415
96986241 2416 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
eef868d1 2417 unassign_capability($capability, $roleid, $contextid);
96986241 2418 return true;
98882637 2419 }
eef868d1 2420
f33e1ed4 2421 $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
e7876c1e 2422
2423 if ($existing and !$overwrite) { // We want to keep whatever is there already
2424 return true;
2425 }
2426
365a5941 2427 $cap = new stdClass();
bbbf2d40 2428 $cap->contextid = $contextid;
2429 $cap->roleid = $roleid;
2430 $cap->capability = $capability;
2431 $cap->permission = $permission;
2432 $cap->timemodified = time();
9db12da7 2433 $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
e7876c1e 2434
2435 if ($existing) {
2436 $cap->id = $existing->id;
4f0c2d00 2437 $DB->update_record('role_capabilities', $cap);
e7876c1e 2438 } else {
f33e1ed4 2439 $c = $DB->get_record('context', array('id'=>$contextid));
4f0c2d00 2440 $DB->insert_record('role_capabilities', $cap);
e7876c1e 2441 }
4f0c2d00 2442 return true;
bbbf2d40 2443}
2444
bbbf2d40 2445/**
2446 * Unassign a capability from a role.
117bd748 2447 *
cc3edaa4 2448 * @global object
46808d7c 2449 * @param int $roleid the role id
2450 * @param string $capability the name of the capability
2451 * @return boolean success or failure
bbbf2d40 2452 */
2453function unassign_capability($capability, $roleid, $contextid=NULL) {
f33e1ed4 2454 global $DB;
eef868d1 2455
4f0c2d00 2456 if (!empty($contextid)) {
c345bb58 2457 // delete from context rel, if this is the last override in this context
4f0c2d00 2458 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
98882637 2459 } else {
4f0c2d00 2460 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
98882637 2461 }
4f0c2d00 2462 return true;
bbbf2d40 2463}
2464
2465
2466/**
46808d7c 2467 * Get the roles that have a given capability assigned to it
759ac72d 2468 * Get the roles that have a given capability assigned to it. This function
2469 * does not resolve the actual permission of the capability. It just checks
f9cd798c 2470 * for assignment only. Use get_roles_with_cap_in_context() if resolution is required.
cc3edaa4 2471 *
2472 * @global object
2473 * @global object
46808d7c 2474 * @param string $capability - capability name (string)
4f0c2d00 2475 * @param string $permission - optional, the permission defined for this capability
46808d7c 2476 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL
2477 * @param object $contect
2478 * @return mixed array or role objects
bbbf2d40 2479 */
4f0c2d00 2480function get_roles_with_capability($capability, $permission=NULL, $context=NULL) {
f33e1ed4 2481 global $CFG, $DB;
eef868d1 2482
f33e1ed4 2483 $params = array();
5a4e7398 2484
ec7a8b79 2485 if ($context) {
2486 if ($contexts = get_parent_contexts($context)) {
2487 $listofcontexts = '('.implode(',', $contexts).')';
2488 } else {
21c9bace 2489 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
eef868d1 2490 $listofcontexts = '('.$sitecontext->id.')'; // must be site
2491 }
f33e1ed4 2492 $contextstr = "AND (rc.contextid = ? OR rc.contextid IN $listofcontexts)";
2493 $params[] = $context->id;
ec7a8b79 2494 } else {
2495 $contextstr = '';
2496 }
eef868d1 2497
2498 $selectroles = "SELECT r.*
5a4e7398 2499 FROM {role} r,
2500 {role_capabilities} rc
f33e1ed4 2501 WHERE rc.capability = ?
5a4e7398 2502 AND rc.roleid = r.id $contextstr";
bbbf2d40 2503
f33e1ed4 2504 array_unshift($params, $capability);
2505
bbbf2d40 2506 if (isset($permission)) {
f33e1ed4 2507 $selectroles .= " AND rc.permission = ?";
2508 $params[] = $permission;
bbbf2d40 2509 }
f33e1ed4 2510 return $DB->get_records_sql($selectroles, $params);
bbbf2d40 2511}
2512
2513
2514/**
df997f84 2515 * This function makes a role-assignment (a role for a user in a particular context)
117bd748 2516 *
46808d7c 2517 * @param int $roleid the role of the id
2518 * @param int $userid userid
46808d7c 2519 * @param int $contextid id of the context
df997f84
PS
2520 * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
2521 * @prama int $itemid id of enrolment/auth plugin
2522 * @param string $timemodified defaults to current time
2523 * @return int new/existing id of the assignment
bbbf2d40 2524 */
df997f84 2525function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
f33e1ed4 2526 global $USER, $CFG, $DB;
bbbf2d40 2527
df997f84
PS
2528 // first of all detect if somebody is using old style parameters
2529 if ($contextid === 0 or is_numeric($component)) {
2530 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
2531 }
a9e1c058 2532
df997f84 2533 // now validate all parameters
bbbf2d40 2534 if (empty($roleid)) {
df997f84 2535 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
bbbf2d40 2536 }
2537
e8c2189d 2538 if (empty($userid)) {
df997f84 2539 throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
bbbf2d40 2540 }
eef868d1 2541
df997f84
PS
2542 if ($itemid) {
2543 if (strpos($component, '_') === false) {
2544 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
2545 }
2546 } else {
2547 $itemid = 0;
2548 if ($component !== '' and strpos($component, '_') === false) {
2549 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2550 }
7700027f 2551 }
bbbf2d40 2552
df997f84
PS
2553 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
2554 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
a9e1c058 2555 return false;
bbbf2d40 2556 }
2557
df997f84 2558 $context = get_context_instance_by_id($contextid, MUST_EXIST);
a9e1c058 2559
69b0088c 2560 if (!$timemodified) {
c421ad4b 2561 $timemodified = time();
69b0088c 2562 }
7700027f 2563
a9e1c058 2564/// Check for existing entry
df997f84
PS
2565 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
2566
2567 if ($ras) {
2568 // role already assigned - this should not happen
2569 if (count($ras) > 1) {
2570 //very weird - remove all duplicates!
2571 $ra = array_shift($ras);
2572 foreach ($ras as $r) {
2573 $DB->delete_records('role_assignments', array('id'=>$r->id));
2574 }
2575 } else {
2576 $ra = reset($ras);
2577 }
128f0984 2578
df997f84
PS
2579 // actually there is no need to update, reset anything or trigger any event, so just return
2580 return $ra->id;
96608a55 2581 }
c421ad4b 2582
df997f84 2583 // Create a new entry
365a5941 2584 $ra = new stdClass();
df997f84
PS
2585 $ra->roleid = $roleid;
2586 $ra->contextid = $context->id;
2587 $ra->userid = $userid;
2588 $ra->component = $component;
2589 $ra->itemid = $itemid;
2590 $ra->timemodified = $timemodified;
2591 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
eef868d1 2592
df997f84
PS
2593 $ra->id = $DB->insert_record('role_assignments', $ra);
2594
2595 // mark context as dirty - again expensive, but needed
2596 mark_context_dirty($context->path);
2597
2598 if (!empty($USER->id) && $USER->id == $userid) {
2599 // If the user is the current user, then do full reload of capabilities too.
2600 load_all_capabilities();
4e5f3064 2601 }
6eb4f823 2602
96608a55 2603 events_trigger('role_assigned', $ra);
2604
386c151e 2605 return $ra->id;
bbbf2d40 2606}
2607
bbbf2d40 2608/**
df997f84 2609 * Removes one role assignment
cc3edaa4 2610 *
df997f84
PS
2611 * @param int $roleid
2612 * @param int $userid
2613 * @param int $contextid
2614 * @param string $component
2615 * @param int $itemid
2616 * @return void
bbbf2d40 2617 */
df997f84 2618function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
5a4e7398 2619 global $USER, $CFG, $DB;
5a4e7398 2620
df997f84
PS
2621 // first make sure the params make sense
2622 if ($roleid == 0 or $userid == 0 or $contextid == 0) {
2623 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
6bc1e5d5 2624 }
d74067e8 2625
df997f84
PS
2626 if ($itemid) {
2627 if (strpos($component, '_') === false) {
2628 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
2629 }
2630 } else {
2631 $itemid = 0;
2632 if ($component !== '' and strpos($component, '_') === false) {
2633 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
4f0c2d00
PS
2634 }
2635 }
2636
df997f84 2637 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
4f0c2d00
PS
2638}
2639
2640/**
df997f84
PS
2641 * Removes multiple role assignments, parameters may contain:
2642 * 'roleid', 'userid', 'contextid', 'component', 'enrolid'.
4f0c2d00 2643 *
df997f84
PS
2644 * @param array $params role assignment parameters
2645 * @param bool $subcontexts unassign in subcontexts too
2646 * @param bool $includmanual include manual role assignments too
2647 * @return void
4f0c2d00 2648 */
df997f84
PS
2649function role_unassign_all(array $params, $subcontexts = false, $includemanual=false) {
2650 global $USER, $CFG, $DB;
4f0c2d00 2651
df997f84
PS
2652 if (!$params) {
2653 throw new coding_exception('Missing parameters in role_unsassign_all() call');
4f0c2d00
PS
2654 }
2655
df997f84
PS
2656 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
2657 foreach ($params as $key=>$value) {
2658 if (!in_array($key, $allowed)) {
2659 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
2660 }
2661 }
4f0c2d00 2662
df997f84
PS
2663 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
2664 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
2665 }
4f0c2d00 2666
df997f84
PS
2667 if ($includemanual) {
2668 if (!isset($params['component']) or $params['component'] === '') {
2669 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
4f0c2d00 2670 }
df997f84 2671 }
4f0c2d00 2672
df997f84
PS
2673 if ($subcontexts) {
2674 if (empty($params['contextid'])) {
2675 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
2676 }
2677 }
4f0c2d00 2678
df997f84
PS
2679 $ras = $DB->get_records('role_assignments', $params);
2680 foreach($ras as $ra) {
2681 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2682 if ($context = get_context_instance_by_id($ra->contextid)) {
2683 // this is a bit expensive but necessary
2684 mark_context_dirty($context->path);
2685 /// If the user is the current user, then do full reload of capabilities too.
2686 if (!empty($USER->id) && $USER->id == $ra->userid) {
2687 load_all_capabilities();
2688 }
2689 }
2690 events_trigger('role_unassigned', $ra);
2691 }
2692 unset($ras);
4f0c2d00 2693
df997f84
PS
2694 // process subcontexts
2695 if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) {
2696 $contexts = get_child_contexts($context);
2697 $mparams = $params;
2698 foreach($contexts as $context) {
2699 $mparams['contextid'] = $context->id;
2700 $ras = $DB->get_records('role_assignments', $mparams);
2701 foreach($ras as $ra) {
2702 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2703 // this is a bit expensive but necessary
2704 mark_context_dirty($context->path);
2705 /// If the user is the current user, then do full reload of capabilities too.
2706 if (!empty($USER->id) && $USER->id == $ra->userid) {
2707 load_all_capabilities();
2708 }
2709 events_trigger('role_unassigned', $ra);
2710 }
2711 }
4f0c2d00
PS
2712 }
2713
df997f84
PS
2714 // do this once more for all manual role assignments
2715 if ($includemanual) {
2716 $params['component'] = '';
2717 role_unassign_all($params, $subcontexts, false);
2718 }
4f0c2d00
PS
2719}
2720
df997f84 2721
4f0c2d00
PS
2722/**
2723 * Determines if a user is currently logged in
2724 *
2725 * @return bool
2726 */
2727function isloggedin() {
2728 global $USER;
2729
2730 return (!empty($USER->id));
2731}
2732
2733/**
2734 * Determines if a user is logged in as real guest user with username 'guest'.
2735 *
df997f84 2736 * @param int|object $user mixed user object or id, $USER if not specified
4f0c2d00
PS
2737 * @return bool true if user is the real guest user, false if not logged in or other user
2738 */
2739function isguestuser($user = NULL) {
2740 global $USER, $DB, $CFG;
2741
2742 // make sure we have the user id cached in config table, because we are going to use it a lot
2743 if (empty($CFG->siteguest)) {
2744 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
2745 // guest does not exist yet, weird
2746 return false;
2747 }
2748 set_config('siteguest', $guestid);
2749 }
2750 if ($user === NULL) {
2751 $user = $USER;
2752 }
2753
2754 if ($user === NULL) {
2755 // happens when setting the $USER
2756 return false;
2757
2758 } else if (is_numeric($user)) {
2759 return ($CFG->siteguest == $user);
2760
2761 } else if (is_object($user)) {
2762 if (empty($user->id)) {
2763 return false; // not logged in means is not be guest
2764 } else {
2765 return ($CFG->siteguest == $user->id);
2766 }
2767
2768 } else {
2769 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
2770 }
2771}
2772
2773/**
2774 * Does user have a (temporary or real) guest access to course?
2775 *
2776 * @param object $context
2777 * @param object|int $user
2778 * @return bool
2779 */
2780function is_guest($context, $user = NULL) {
5fd9e798
PS
2781 global $USER;
2782
4f0c2d00
PS
2783 // first find the course context
2784 $coursecontext = get_course_context($context);
2785
2786 // make sure there is a real user specified
2787 if ($user === NULL) {
2788 $userid = !empty($USER->id) ? $USER->id : 0;
2789 } else {
2790 $userid = !empty($user->id) ? $user->id : $user;
2791 }
2792
2793 if (isguestuser($userid)) {
2794 // can not inspect or be enrolled
2795 return true;
2796 }
2797
2798 if (has_capability('moodle/course:view', $coursecontext, $user)) {
2799 // viewing users appear out of nowhere, they are neither guests nor participants
2800 return false;
2801 }
2802
df997f84
PS
2803 // consider only real active enrolments here
2804 if (is_enrolled($coursecontext, $user, '', true)) {
4f0c2d00
PS
2805 return false;
2806 }
2807
2808 return true;
2809}
2810
2811
2812/**
2418d71e 2813 * Returns true if the user has moodle/course:view capability in the course,
4f0c2d00
PS
2814 * this is intended for admins, managers (aka small admins), inspectors, etc.
2815 *
2816 * @param object $context
2817 * @param int|object $user, if NULL $USER is used
2818 * @param string $withcapability extra capability name
2819 * @return bool
2820 */
2821function is_viewing($context, $user = NULL, $withcapability = '') {
2822 global $USER;
2823
2824 // first find the course context
2825 $coursecontext = get_course_context($context);
2826
2827 if (isguestuser($user)) {
2828 // can not inspect
83bbafaa 2829 return false;
4f0c2d00
PS
2830 }
2831
2832 if (!has_capability('moodle/course:view', $coursecontext, $user)) {
2833 // admins are allowed to inspect courses
2834 return false;
2835 }
2836
2837 if ($withcapability and !has_capability($withcapability, $context, $user)) {
2838 // site admins always have the capability, but the enrolment above blocks
2839 return false;
2840 }
2841
2842 return true;
2843}
2844
2845/**
2846 * Returns true if user is enrolled (is participating) in course
2847 * this is intended for students and teachers.
2848 *
2849 * @param object $context
4f65e0fb 2850 * @param int|object $user, if NULL $USER is used, otherwise user object or id expected
4f0c2d00 2851 * @param string $withcapability extra capability name
df997f84 2852 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
4f0c2d00
PS
2853 * @return bool
2854 */
df997f84
PS
2855function is_enrolled($context, $user = NULL, $withcapability = '', $onlyactive = false) {
2856 global $USER, $DB;
4f0c2d00
PS
2857
2858 // first find the course context
2859 $coursecontext = get_course_context($context);
2860
2861 // make sure there is a real user specified
2862 if ($user === NULL) {
2863 $userid = !empty($USER->id) ? $USER->id : 0;
2864 } else {
2865 $userid = !empty($user->id) ? $user->id : $user;
2866 }
2867
2868 if (empty($userid)) {
2869 // not-logged-in!
2870 return false;
2871 } else if (isguestuser($userid)) {
2872 // guest account can not be enrolled anywhere
2873 return false;
2874 }
2875
df997f84
PS
2876 if ($coursecontext->instanceid == SITEID) {
2877 // everybody participates on frontpage
2878 } else {
2879 if ($onlyactive) {
2880 $sql = "SELECT ue.*
2881 FROM {user_enrolments} ue
2882 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2883 JOIN {user} u ON u.id = ue.userid
2884 WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
2885 $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2886 // this result should be very small, better not do the complex time checks in sql for now ;-)
2887 $enrolments = $DB->get_records_sql($sql, $params);
2888 $now = time();
2889 // make sure the enrol period is ok
2890 $result = false;
2891 foreach ($enrolments as $e) {
2892 if ($e->timestart > $now) {
2893 continue;
2894 }
2895 if ($e->timeend and $e->timeend < $now) {
2896 continue;
2897 }
2898 $result = true;
2899 break;
2900 }
2901 if (!$result) {
2902 return false;
2903 }
2904
2905 } else {
2906 // any enrolment is good for us here, even outdated, disabled or inactive
2907 $sql = "SELECT 'x'
2908 FROM {user_enrolments} ue
2909 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2910 JOIN {user} u ON u.id = ue.userid
2911 WHERE ue.userid = :userid AND u.deleted = 0";
2912 $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2913 if (!$DB->record_exists_sql($sql, $params)) {
2914 return false;
2915 }
2916 }
4f0c2d00
PS
2917 }
2918
2919 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2920 return false;
2921 }
2922
2923 return true;
2924}
2925
2926/**
2927 * Returns array with sql code and parameters returning all ids
2928 * of users enrolled into course.
df997f84
PS
2929 *
2930 * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2931 *
4f0c2d00
PS
2932 * @param object $context
2933 * @param string $withcapability
2934 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
df997f84 2935 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
4f0c2d00
PS
2936 * @return array list($sql, $params)
2937 */
df997f84 2938function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) {
b3df1764 2939 global $DB, $CFG;
4f0c2d00 2940
df997f84
PS
2941 // use unique prefix just in case somebody makes some SQL magic with the result
2942 static $i = 0;
2943 $i++;
2944 $prefix = 'eu'.$i.'_';
4f0c2d00
PS
2945
2946 // first find the course context
df997f84 2947 $coursecontext = get_course_context($context);
4f0c2d00 2948
df997f84 2949 $isfrontpage = ($coursecontext->instanceid == SITEID);
4f0c2d00 2950
df997f84
PS
2951 $joins = array();
2952 $wheres = array();
2953 $params = array();
4f0c2d00
PS
2954
2955 list($contextids, $contextpaths) = get_context_info_list($context);
4f0c2d00
PS
2956
2957 // get all relevant capability info for all roles
2958 if ($withcapability) {
df997f84
PS
2959 list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx00');
2960 $cparams['cap'] = $withcapability;
2961
2962 $defs = array();
2963 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
2964 FROM {role_capabilities} rc
2965 JOIN {context} ctx on rc.contextid = ctx.id
2966 WHERE rc.contextid $incontexts AND rc.capability = :cap";
2967 $rcs = $DB->get_records_sql($sql, $cparams);
2968 foreach ($rcs as $rc) {
2969 $defs[$rc->path][$rc->roleid] = $rc->permission;
4f0c2d00 2970 }
4f0c2d00 2971
df997f84
PS
2972 $access = array();
2973 if (!empty($defs)) {
2974 foreach ($contextpaths as $path) {
2975 if (empty($defs[$path])) {
4f0c2d00
PS
2976 continue;
2977 }
df997f84
PS
2978 foreach($defs[$path] as $roleid => $perm) {
2979 if ($perm == CAP_PROHIBIT) {
2980 $access[$roleid] = CAP_PROHIBIT;
2981 continue;
2982 }
2983 if (!isset($access[$roleid])) {
2984 $access[$roleid] = (int)$perm;
2985 }
4f0c2d00
PS
2986 }
2987 }
2988 }
4f0c2d00 2989
df997f84 2990 unset($defs);
4f0c2d00 2991
df997f84
PS
2992 // make lists of roles that are needed and prohibited
2993 $needed = array(); // one of these is enough
2994 $prohibited = array(); // must not have any of these
fafa57e9
PS
2995 foreach ($access as $roleid => $perm) {
2996 if ($perm == CAP_PROHIBIT) {
2997 unset($needed[$roleid]);
2998 $prohibited[$roleid] = true;
2999 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
3000 $needed[$roleid] = true;
4f0c2d00
PS
3001 }
3002 }
4f0c2d00 3003
df997f84
PS
3004 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : NULL;
3005 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : NULL;
2515adf9 3006
df997f84 3007 $nobody = false;
2515adf9 3008
4f0c2d00
PS
3009 if ($isfrontpage) {
3010 if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
3011 $nobody = true;
fafa57e9 3012 } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
4f0c2d00
PS
3013 // everybody not having prohibit has the capability
3014 $needed = array();
3015 } else if (empty($needed)) {
3016 $nobody = true;
3017 }
3018 } else {
3019 if (!empty($prohibited[$defaultuserroleid])) {
3020 $nobody = true;
fafa57e9 3021 } else if (!empty($needed[$defaultuserroleid])) {
4f0c2d00
PS
3022 // everybody not having prohibit has the capability
3023 $needed = array();
3024 } else if (empty($needed)) {
3025 $nobody = true;
0f161e1f 3026 }
d74067e8 3027 }
4e5f3064 3028
df997f84
PS
3029 if ($nobody) {
3030 // nobody can match so return some SQL that does not return any results
3031 $wheres[] = "1 = 2";
bbbf2d40 3032
df997f84 3033 } else {
b963384f 3034
df997f84
PS
3035 if ($needed) {
3036 $ctxids = implode(',', $contextids);
3037 $roleids = implode(',', array_keys($needed));
3038 $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))";
3039 }
b963384f 3040
df997f84
PS
3041 if ($prohibited) {
3042 $ctxids = implode(',', $contextids);
3043 $roleids = implode(',', array_keys($prohibited));
3044 $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 3045 $wheres[] = "{$prefix}ra4.id IS NULL";
df997f84 3046 }
c4381ef5 3047
df997f84 3048 if ($groupid) {
c971c34c 3049 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
df997f84
PS
3050 $params["{$prefix}gmid"] = $groupid;
3051 }
3052 }
c4381ef5 3053
fafa57e9
PS
3054 } else {
3055 if ($groupid) {
3056 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
3057 $params["{$prefix}gmid"] = $groupid;
3058 }
4f0c2d00 3059 }
eef868d1 3060
b3df1764
PS
3061 $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
3062 $params["{$prefix}guestid"] = $CFG->siteguest;
df997f84
PS
3063
3064 if ($isfrontpage) {
3065 // all users are "enrolled" on the frontpage
3066 } else {
3067 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
3068 $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
3069 $params[$prefix.'courseid'] = $coursecontext->instanceid;
3070
3071 if ($onlyactive) {
3072 $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
3073 $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
3074 $now = round(time(), -2); // rounding helps caching in DB
3075 $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
3076 $prefix.'active'=>ENROL_USER_ACTIVE,
3077 $prefix.'now1'=>$now, $prefix.'now2'=>$now));
3078 }
4f0c2d00 3079 }
a9d4ea78 3080
4f0c2d00
PS
3081 $joins = implode("\n", $joins);
3082 $wheres = "WHERE ".implode(" AND ", $wheres);
eef868d1 3083
df997f84
PS
3084 $sql = "SELECT DISTINCT {$prefix}u.id
3085 FROM {user} {$prefix}u
4f0c2d00
PS
3086 $joins
3087 $wheres";
b963384f 3088
4f0c2d00
PS
3089 return array($sql, $params);
3090}
3091
3092/**
3093 * Returns list of users enrolled into course.
3094 * @param object $context
3095 * @param string $withcapability
3096 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3097 * @param string $userfields requested user record fields
3098 * @param string $orderby
3099 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
3100 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
3101 * @return array of user records
3102 */
3103function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
3104 global $DB;
3105
3106 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3107 $sql = "SELECT $userfields
3108 FROM {user} u
3109 JOIN ($esql) je ON je.id = u.id
3110 WHERE u.deleted = 0";
3111
3112 if ($orderby) {
3113 $sql = "$sql ORDER BY $orderby";
3114 } else {
3115 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
b963384f 3116 }
3117
4f0c2d00 3118 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
b963384f 3119}
3120
dcc779cc
MD
3121/**
3122 * Counts list of users enrolled into course (as per above function)
3123 * @param object $context
3124 * @param string $withcapability
3125 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3126 * @return array of user records
3127 */
3128function count_enrolled_users($context, $withcapability = '', $groupid = 0) {
3129 global $DB;
3130
3131 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3132 $sql = "SELECT count(u.id)
3133 FROM {user} u
3134 JOIN ($esql) je ON je.id = u.id
3135 WHERE u.deleted = 0";
3136
3137 return $DB->count_records_sql($sql, $params);
3138}
3139
3140
bbbf2d40 3141/**
46808d7c 3142 * Loads the capability definitions for the component (from file).
3143 *
bbbf2d40 3144 * Loads the capability definitions for the component (from file). If no
3145 * capabilities are defined for the component, we simply return an empty array.
46808d7c 3146 *
cc3edaa4 3147 * @global object
17da2e6f 3148 * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
46808d7c 3149 * @return array array of capabilities
bbbf2d40 3150 */
3151function load_capability_def($component) {