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