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