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