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