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