Updated the HEAD build version to 20100405
[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 *
92e53168 111 * Changes at the sytem level will force the reload for everyone.
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.
cc3edaa4 182 * Sadly, a PHP global variale is the only way to impleemnt this, withough rewriting everything
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,
204 * and also at the end fo 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/**
41e87d30 437 * Check whether a user has a paritcular 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 *
41e87d30 443 * By default checks the capabilties 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 * ---------------------
787 * Originaly there was an extremely complicated way
788 * to determine the user access that dealt with
789 * "locality" or role assignemnts and role overrides.
790 * Now we simply evaluate access for each roel separately
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 *
956 * - walk the courses recordset checking the caps oneach one
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/**
46808d7c 1541 * Use shared copy of role definistions 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/**
1813 * Assign the defaults found in this capabality definition to roles that have
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/**
1855 * @param object $capability a capbility - a row from the capabilitites table.
1856 * @return boolean whether this capability is safe - that is, wether people with the
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/**
46808d7c 1868 * Create a new context record for use by all roles-related stuff
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 *
cc3edaa4 1873 * @global object
1874 * @global object
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 *
2092 * @global object
2093 * @global object
46808d7c 2094 * @param int $level
2095 * @param int $instanceid
17b0efae 2096 * @return bool properly deleted
9991d157 2097 */
2098function delete_context($contextlevel, $instanceid) {
8432f5e6 2099 global $DB, $ACCESSLIB_PRIVATE, $CFG;
b51ece5b 2100
2101 // do not use get_context_instance(), because the related object might not exist,
2102 // or the context does not exist yet and it would be created now
5a4e7398 2103 if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
568df475 2104 $result = $DB->delete_records('role_assignments', array('contextid'=>$context->id)) &&
2105 $DB->delete_records('role_capabilities', array('contextid'=>$context->id)) &&
cae83708 2106 $DB->delete_records('context', array('id'=>$context->id)) &&
bf66a674 2107 $DB->delete_records('role_names', array('contextid'=>$context->id));
b51ece5b 2108
2109 // do not mark dirty contexts if parents unknown
2110 if (!is_null($context->path) and $context->depth > 0) {
2111 mark_context_dirty($context->path);
2112 }
2113
2114 // purge static context cache if entry present
d867e696 2115 unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]);
2116 unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]);
b51ece5b 2117
6bf44482 2118 blocks_delete_all_for_context($context->id);
9434fef4 2119 filter_delete_all_for_context($context->id);
2120
b51ece5b 2121 return $result;
2122 } else {
2123
2124 return true;
9991d157 2125 }
9991d157 2126}
2127
9a81a606 2128/**
2129 * Precreates all contexts including all parents
cc3edaa4 2130 *
2131 * @global object
46808d7c 2132 * @param int $contextlevel empty means all
9a81a606 2133 * @param bool $buildpaths update paths and depths
2134 * @return void
2135 */
4f0c2d00 2136function create_contexts($contextlevel=NULL, $buildpaths=true) {
5a4e7398 2137 global $DB;
9a81a606 2138
2139 //make sure system context exists
2140 $syscontext = get_system_context(false);
2141
5c8e6cb1 2142 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2143 or $contextlevel == CONTEXT_COURSE
2144 or $contextlevel == CONTEXT_MODULE
2145 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 2146 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 2147 SELECT ".CONTEXT_COURSECAT.", cc.id
5a4e7398 2148 FROM {course}_categories cc
9a81a606 2149 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 2150 FROM {context} cx
9a81a606 2151 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
5a4e7398 2152 $DB->execute($sql);
9a81a606 2153
2154 }
2155
5c8e6cb1 2156 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2157 or $contextlevel == CONTEXT_MODULE
2158 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 2159 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 2160 SELECT ".CONTEXT_COURSE.", c.id
5a4e7398 2161 FROM {course} c
9a81a606 2162 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 2163 FROM {context} cx
9a81a606 2164 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
5a4e7398 2165 $DB->execute($sql);
9a81a606 2166
2167 }
2168
e92c286c 2169 if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
2170 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 2171 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 2172 SELECT ".CONTEXT_MODULE.", cm.id
5a4e7398 2173 FROM {course}_modules cm
9a81a606 2174 WHERE NOT EXISTS (SELECT 'x'
5a4e7398 2175 FROM {context} cx
9a81a606 2176 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
5a4e7398 2177 $DB->execute($sql);
9a81a606 2178 }
2179
e92c286c 2180 if (empty($contextlevel) or $contextlevel == CONTEXT_USER
2181 or $contextlevel == CONTEXT_BLOCK) {
5a4e7398 2182 $sql = "INSERT INTO {context} (contextlevel, instanceid)
9a81a606 2183 SELECT ".CONTEXT_USER.", u.id
5a4e7398 2184 FROM {user} u
9a81a606 2185 WHERE u.deleted=0
2186 AND NOT EXISTS (SELECT 'x'
5a4e7398 2187 FROM {context} cx
9a81a606 2188 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
5a4e7398 2189 $DB->execute($sql);
9a81a606 2190
2191 }
2192
e92c286c 2193 if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
2194 $sql = "INSERT INTO {context} (contextlevel, instanceid)
2195 SELECT ".CONTEXT_BLOCK.", bi.id
2196 FROM {block_instances} bi
2197 WHERE NOT EXISTS (SELECT 'x'
2198 FROM {context} cx
2199 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2200 $DB->execute($sql);
2201 }
2202
9a81a606 2203 if ($buildpaths) {
5a4e7398 2204 build_context_path(false);
9a81a606 2205 }
2206}
2207
17b0efae 2208/**
2209 * Remove stale context records
2210 *
cc3edaa4 2211 * @global object
17b0efae 2212 * @return bool
2213 */
2214function cleanup_contexts() {
5a4e7398 2215 global $DB;
17b0efae 2216
70dd126e 2217 $sql = " SELECT c.contextlevel,
17b0efae 2218 c.instanceid AS instanceid
5a4e7398 2219 FROM {context} c
2220 LEFT OUTER JOIN {course}_categories t
2221 ON c.instanceid = t.id
2222 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
17b0efae 2223 UNION
70dd126e 2224 SELECT c.contextlevel,
2225 c.instanceid
5a4e7398 2226 FROM {context} c
2227 LEFT OUTER JOIN {course} t
2228 ON c.instanceid = t.id
2229 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
17b0efae 2230 UNION
70dd126e 2231 SELECT c.contextlevel,
2232 c.instanceid
5a4e7398 2233 FROM {context} c
2234 LEFT OUTER JOIN {course}_modules t
2235 ON c.instanceid = t.id
2236 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
17b0efae 2237 UNION
70dd126e 2238 SELECT c.contextlevel,
2239 c.instanceid
5a4e7398 2240 FROM {context} c
2241 LEFT OUTER JOIN {user} t
2242 ON c.instanceid = t.id
2243 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
17b0efae 2244 UNION
70dd126e 2245 SELECT c.contextlevel,
2246 c.instanceid
5a4e7398 2247 FROM {context} c
f474a4e5 2248 LEFT OUTER JOIN {block_instances} t
2249 ON c.instanceid = t.id
5a4e7398 2250 WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
17b0efae 2251 ";
d5a8d9aa
PS
2252
2253 // transactions used only for performance reasons here
2254 $transaction = $DB->start_delegated_transaction();
2255
5a4e7398 2256 if ($rs = $DB->get_recordset_sql($sql)) {
5a4e7398 2257 foreach ($rs as $ctx) {
d5a8d9aa 2258 delete_context($ctx->contextlevel, $ctx->instanceid);
17b0efae 2259 }
5a4e7398 2260 $rs->close();
17b0efae 2261 }
d5a8d9aa
PS
2262
2263 $transaction->allow_commit();
17b0efae 2264 return true;
2265}
2266
00653161 2267/**
e92c286c 2268 * Preloads all contexts relating to a course: course, modules. Block contexts
2269 * are no longer loaded here. The contexts for all the blocks on the current
2270 * page are now efficiently loaded by {@link block_manager::load_blocks()}.
00653161 2271 *
2272 * @param int $courseid Course ID
d993468d 2273 * @return void
00653161 2274 */
2275function preload_course_contexts($courseid) {
d867e696 2276 global $DB, $ACCESSLIB_PRIVATE;
00653161 2277
2278 // Users can call this multiple times without doing any harm
d867e696 2279 global $ACCESSLIB_PRIVATE;
2280 if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
00653161 2281 return;
2282 }
2283
d993468d 2284 $params = array($courseid, $courseid, $courseid);
2285 $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2286 FROM {course_modules} cm
2287 JOIN {context} x ON x.instanceid=cm.id
2288 WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2289
d993468d 2290 UNION ALL
2291
2292 SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2293 FROM {context} x
2294 WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2295
2296 $rs = $DB->get_recordset_sql($sql, $params);
00653161 2297 foreach($rs as $context) {
d867e696 2298 cache_context($context);
00653161 2299 }
2300 $rs->close();
d867e696 2301 $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
00653161 2302}
2303
bbbf2d40 2304/**
2305 * Get the context instance as an object. This function will create the
2306 * context instance if it does not exist yet.
46808d7c 2307 *
2308 * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2309 *
e765b5d3 2310 * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2311 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
46808d7c 2312 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
4f0c2d00
PS
2313 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2314 * MUST_EXIST means throw exception if no record or multiple records found
e765b5d3 2315 * @return object The context object.
bbbf2d40 2316 */
4f0c2d00 2317function get_context_instance($contextlevel, $instance=0, $strictness=IGNORE_MISSING) {
e5605780 2318
d867e696 2319 global $DB, $ACCESSLIB_PRIVATE;
8ead7b59 2320 static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
d9a35e12 2321
56743fab 2322 if ($contextlevel === 'clearcache') {
2323 // TODO: Remove for v2.0
5a4e7398 2324 // No longer needed, but we'll catch it to avoid erroring out on custom code.
2325 // This used to be a fix for MDL-9016
2326 // "Restoring into existing course, deleting first
56743fab 2327 // deletes context and doesn't recreate it"
9251b26f 2328 return false;
2329 }
b7cec865 2330
7d0c81b3 2331/// System context has special cache
8ba412da 2332 if ($contextlevel == CONTEXT_SYSTEM) {
7d0c81b3 2333 return get_system_context();
8ba412da 2334 }
2335
a36a3a3f 2336/// check allowed context levels
2337 if (!in_array($contextlevel, $allowed_contexts)) {
7bfa3101 2338 // fatal error, code must be fixed - probably typo or switched parameters
e49ef64a 2339 print_error('invalidcourselevel');
a36a3a3f 2340 }
2341
65bcf17b 2342 if (!is_array($instance)) {
2343 /// Check the cache
d867e696 2344 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2345 return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2346 }
2347
2348 /// Get it from the database, or create it
5a4e7398 2349 if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
4f0c2d00 2350 $context = create_context($contextlevel, $instance, $strictness);
65bcf17b 2351 }
2352
2353 /// Only add to cache if context isn't empty.
2354 if (!empty($context)) {
d867e696 2355 cache_context($context);
65bcf17b 2356 }
2357
2358 return $context;
e5605780 2359 }
2360
65bcf17b 2361
2362/// ok, somebody wants to load several contexts to save some db queries ;-)
2363 $instances = $instance;
2364 $result = array();
2365
2366 foreach ($instances as $key=>$instance) {
2367 /// Check the cache first
d867e696 2368 if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached
2369 $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
65bcf17b 2370 unset($instances[$key]);
2371 continue;
2372 }
e5605780 2373 }
2374
65bcf17b 2375 if ($instances) {
5a4e7398 2376 list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2377 array_unshift($params, $contextlevel);
2378 $sql = "SELECT instanceid, id, contextlevel, path, depth
2379 FROM {context}
2380 WHERE contextlevel=? AND instanceid $instanceids";
2381
2382 if (!$contexts = $DB->get_records_sql($sql, $params)) {
65bcf17b 2383 $contexts = array();
2384 }
2385
2386 foreach ($instances as $instance) {
2387 if (isset($contexts[$instance])) {
2388 $context = $contexts[$instance];
2389 } else {
2390 $context = create_context($contextlevel, $instance);
2391 }
2392
2393 if (!empty($context)) {
d867e696 2394 cache_context($context);
65bcf17b 2395 }
2396
2397 $result[$instance] = $context;
2398 }
ccfc5ecc 2399 }
0468976c 2400
65bcf17b 2401 return $result;
bbbf2d40 2402}
2403
cee0901c 2404
340ea4e8 2405/**
e765b5d3 2406 * Get a context instance as an object, from a given context id.
cc3edaa4 2407 *
65bcf17b 2408 * @param mixed $id a context id or array of ids.
01a2ce80
PS
2409 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2410 * MUST_EXIST means throw exception if no record or multiple records found
46808d7c 2411 * @return mixed object, array of the context object, or false.
340ea4e8 2412 */
01a2ce80 2413function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) {
d867e696 2414 global $DB, $ACCESSLIB_PRIVATE;
d9a35e12 2415
7d0c81b3 2416 if ($id == SYSCONTEXTID) {
2417 return get_system_context();
2418 }
2419
d867e696 2420 if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) { // Already cached
2421 return $ACCESSLIB_PRIVATE->contextsbyid[$id];
340ea4e8 2422 }
2423
01a2ce80 2424 if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
d867e696 2425 cache_context($context);
340ea4e8 2426 return $context;
2427 }
2428
2429 return false;
2430}
2431
bbbf2d40 2432
8737be58 2433/**
2434 * Get the local override (if any) for a given capability in a role in a context
cc3edaa4 2435 *
2436 * @global object
46808d7c 2437 * @param int $roleid
2438 * @param int $contextid
2439 * @param string $capability
8737be58 2440 */
2441function get_local_override($roleid, $contextid, $capability) {
5a4e7398 2442 global $DB;
2443 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
8737be58 2444}
2445
01a2ce80
PS
2446/**
2447 * Returns context instance plus related course and cm instances
2448 * @param int $contextid
2449 * @return array of ($context, $course, $cm)
2450 */
2451function get_context_info_array($contextid) {
2452 global $DB;
2453
2454 $context = get_context_instance_by_id($contextid, MUST_EXIST);
4f0c2d00
PS
2455 $course = NULL;
2456 $cm = NULL;
01a2ce80
PS
2457
2458 if ($context->contextlevel == CONTEXT_COURSE) {
2459 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2460
2461 } else if ($context->contextlevel == CONTEXT_MODULE) {
2462 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2463 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2464
2465 } else if ($context->contextlevel == CONTEXT_BLOCK) {
2466 $parentcontexts = get_parent_contexts($context, false);
2467 $parent = reset($parentcontexts);
2468 $parent = get_context_instance_by_id($parent);
2469
2470 if ($parent->contextlevel == CONTEXT_COURSE) {
2471 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2472 } else if ($parent->contextlevel == CONTEXT_MODULE) {
2473 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2474 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2475 }
2476 }
2477
2478 return array($context, $course, $cm);
2479}
8737be58 2480
bbbf2d40 2481
46808d7c 2482//////////////////////////////////////
2483// DB TABLE RELATED FUNCTIONS //
2484//////////////////////////////////////
bbbf2d40 2485
cee0901c 2486/**
bbbf2d40 2487 * function that creates a role
cc3edaa4 2488 *
2489 * @global object
46808d7c 2490 * @param string $name role name
2491 * @param string $shortname role short name
2492 * @param string $description role description
4f0c2d00 2493 * @param string $archetype
46808d7c 2494 * @return mixed id or dml_exception
bbbf2d40 2495 */
4f0c2d00 2496function create_role($name, $shortname, $description, $archetype='') {
f33e1ed4 2497 global $DB;
eef868d1 2498
4f0c2d00
PS
2499 if (strpos($archetype, 'moodle/legacy:') !== false) {
2500 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
2501 }
2502
2503 // verify role archetype actually exists
2504 $archetypes = get_role_archetypes();
2505 if (empty($archetypes[$archetype])) {
2506 $archetype = '';
2507 }
2508
bbdb7070 2509 // Get the system context.
19a4a32e 2510 $context = get_context_instance(CONTEXT_SYSTEM);
31f26796 2511
bbdb7070 2512 // Insert the role record.
b5959f30 2513 $role = new object();
ac173d3e 2514 $role->name = $name;
2515 $role->shortname = $shortname;
98882637 2516 $role->description = $description;
4f0c2d00 2517 $role->archetype = $archetype;
eef868d1 2518
8420bee9 2519 //find free sortorder number
bbdb7070 2520 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
716dd163 2521 if (empty($role->sortorder)) {
2522 $role->sortorder = 1;
2523 }
bbdb7070 2524 $id = $DB->insert_record('role', $role);
b5959f30 2525
bbdb7070 2526 return $id;
bbbf2d40 2527}
2528
8420bee9 2529/**
46808d7c 2530 * Function that deletes a role and cleanups up after it
cc3edaa4 2531 *
46808d7c 2532 * @param int $roleid id of role to delete
4f0c2d00 2533 * @return bool lways true
8420bee9 2534 */
2535function delete_role($roleid) {
f33e1ed4 2536 global $CFG, $DB;
c421ad4b 2537
4f0c2d00
PS
2538 // first unssign all users
2539 role_unassign($roleid);
c421ad4b 2540
4f0c2d00
PS
2541 // cleanup all references to this role, ignore errors
2542 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
2543 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
2544 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
2545 $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2546 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2547 $DB->delete_records('role_names', array('roleid'=>$roleid));
2548 $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
60ace1e1 2549
4f0c2d00 2550 // finally delete the role itself
cb8cb8bf 2551 // get this before the name is gone for logging
f33e1ed4 2552 $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
5a4e7398 2553
4f0c2d00 2554 $DB->delete_records('role', array('id'=>$roleid));
5a4e7398 2555
4f0c2d00 2556 add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
8420bee9 2557
4f0c2d00 2558 return true;
8420bee9 2559}
2560
bbbf2d40 2561/**
2562 * Function to write context specific overrides, or default capabilities.
46808d7c 2563 *
cc3edaa4 2564 * @global object
2565 * @global object
46808d7c 2566 * @param string module string name
2567 * @param string capability string name
2568 * @param int contextid context id
2569 * @param int roleid role id
2570 * @param int permission int 1,-1 or -1000 should not be writing if permission is 0
2571 * @return bool
bbbf2d40 2572 */
e7876c1e 2573function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
f33e1ed4 2574 global $USER, $DB;
eef868d1 2575
96986241 2576 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
eef868d1 2577 unassign_capability($capability, $roleid, $contextid);
96986241 2578 return true;
98882637 2579 }
eef868d1 2580
f33e1ed4 2581 $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
e7876c1e 2582
2583 if ($existing and !$overwrite) { // We want to keep whatever is there already
2584 return true;
2585 }
2586
bbbf2d40 2587 $cap = new object;
2588 $cap->contextid = $contextid;
2589 $cap->roleid = $roleid;
2590 $cap->capability = $capability;
2591 $cap->permission = $permission;
2592 $cap->timemodified = time();
9db12da7 2593 $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
e7876c1e 2594
2595 if ($existing) {
2596 $cap->id = $existing->id;
4f0c2d00 2597 $DB->update_record('role_capabilities', $cap);
e7876c1e 2598 } else {
f33e1ed4 2599 $c = $DB->get_record('context', array('id'=>$contextid));
4f0c2d00 2600 $DB->insert_record('role_capabilities', $cap);
e7876c1e 2601 }
4f0c2d00 2602 return true;
bbbf2d40 2603}
2604
bbbf2d40 2605/**
2606 * Unassign a capability from a role.
117bd748 2607 *
cc3edaa4 2608 * @global object
46808d7c 2609 * @param int $roleid the role id
2610 * @param string $capability the name of the capability
2611 * @return boolean success or failure
bbbf2d40 2612 */
2613function unassign_capability($capability, $roleid, $contextid=NULL) {
f33e1ed4 2614 global $DB;
eef868d1 2615
4f0c2d00 2616 if (!empty($contextid)) {
c345bb58 2617 // delete from context rel, if this is the last override in this context
4f0c2d00 2618 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
98882637 2619 } else {
4f0c2d00 2620 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
98882637 2621 }
4f0c2d00 2622 return true;
bbbf2d40 2623}
2624
2625
2626/**
46808d7c 2627 * Get the roles that have a given capability assigned to it
759ac72d 2628 * Get the roles that have a given capability assigned to it. This function
2629 * does not resolve the actual permission of the capability. It just checks
2630 * for assignment only.
cc3edaa4 2631 *
2632 * @global object
2633 * @global object
46808d7c 2634 * @param string $capability - capability name (string)
4f0c2d00 2635 * @param string $permission - optional, the permission defined for this capability
46808d7c 2636 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL
2637 * @param object $contect
2638 * @return mixed array or role objects
bbbf2d40 2639 */
4f0c2d00 2640function get_roles_with_capability($capability, $permission=NULL, $context=NULL) {
f33e1ed4 2641 global $CFG, $DB;
eef868d1 2642
f33e1ed4 2643 $params = array();
5a4e7398 2644
ec7a8b79 2645 if ($context) {
2646 if ($contexts = get_parent_contexts($context)) {
2647 $listofcontexts = '('.implode(',', $contexts).')';
2648 } else {
21c9bace 2649 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
eef868d1 2650 $listofcontexts = '('.$sitecontext->id.')'; // must be site
2651 }
f33e1ed4 2652 $contextstr = "AND (rc.contextid = ? OR rc.contextid IN $listofcontexts)";
2653 $params[] = $context->id;
ec7a8b79 2654 } else {
2655 $contextstr = '';
2656 }
eef868d1 2657
2658 $selectroles = "SELECT r.*
5a4e7398 2659 FROM {role} r,
2660 {role_capabilities} rc
f33e1ed4 2661 WHERE rc.capability = ?
5a4e7398 2662 AND rc.roleid = r.id $contextstr";
bbbf2d40 2663
f33e1ed4 2664 array_unshift($params, $capability);
2665
bbbf2d40 2666 if (isset($permission)) {
f33e1ed4 2667 $selectroles .= " AND rc.permission = ?";
2668 $params[] = $permission;
bbbf2d40 2669 }
f33e1ed4 2670 return $DB->get_records_sql($selectroles, $params);
bbbf2d40 2671}
2672
2673
2674/**
a9e1c058 2675 * This function makes a role-assignment (a role for a user or group in a particular context)
117bd748 2676 *
46808d7c 2677 * @param int $roleid the role of the id
2678 * @param int $userid userid
2679 * @param int $groupid group id
2680 * @param int $contextid id of the context
2681 * @param int $timestart time this assignment becomes effective defaults to 0
2682 * @param int $timeend time this assignemnt ceases to be effective defaults to 0
4f0c2d00 2683 * @param int $hidden_ignored - use roels with moodle/course:view capability or enrolemnt instead
46808d7c 2684 * @param string $enrol defaults to 'manual'
2685 * @param string $timemodified defaults to ''
2686 * @return int new id of the assigment
bbbf2d40 2687 */
4f0c2d00 2688function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden_ignored=0, $enrol='manual',$timemodified='') {
f33e1ed4 2689 global $USER, $CFG, $DB;
bbbf2d40 2690
a9e1c058 2691/// Do some data validation
2692
bbbf2d40 2693 if (empty($roleid)) {
9d829c68 2694 debugging('Role ID not provided');
a9e1c058 2695 return false;
bbbf2d40 2696 }
2697
2698 if (empty($userid) && empty($groupid)) {
9d829c68 2699 debugging('Either userid or groupid must be provided');
a9e1c058 2700 return false;
bbbf2d40 2701 }
eef868d1 2702
f67cab32 2703 if ($userid && !$DB->record_exists('user', array('id'=>$userid))) {
82396e5b 2704 debugging('User ID '.intval($userid).' does not exist!');
7700027f 2705 return false;
2706 }
bbbf2d40 2707
f3f7610c 2708 if ($groupid && !groups_group_exists($groupid)) {
82396e5b 2709 debugging('Group ID '.intval($groupid).' does not exist!');
dc411d1b 2710 return false;
2711 }
2712
7700027f 2713 if (!$context = get_context_instance_by_id($contextid)) {
82396e5b 2714 debugging('Context ID '.intval($contextid).' does not exist!');
a9e1c058 2715 return false;
bbbf2d40 2716 }
2717
a9e1c058 2718 if (($timestart and $timeend) and ($timestart > $timeend)) {
9d829c68 2719 debugging('The end time can not be earlier than the start time');
a9e1c058 2720 return false;
2721 }
2722
69b0088c 2723 if (!$timemodified) {
c421ad4b 2724 $timemodified = time();
69b0088c 2725 }
7700027f 2726
a9e1c058 2727/// Check for existing entry
2728 if ($userid) {
f33e1ed4 2729 $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid));
a9e1c058 2730 } else {
f33e1ed4 2731 $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'groupid'=>$groupid));
a9e1c058 2732 }
2733
a9e1c058 2734 if (empty($ra)) { // Create a new entry
96608a55 2735 $ra = new object();
2736 $ra->roleid = $roleid;
2737 $ra->contextid = $context->id;
2738 $ra->userid = $userid;
96608a55 2739 $ra->enrol = $enrol;
c421ad4b 2740 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
0616d3e8 2741 /// by repeating queries with the same exact parameters in a 100 secs time window
96608a55 2742 $ra->timestart = round($timestart, -2);
2743 $ra->timeend = $timeend;
2744 $ra->timemodified = $timemodified;
2745 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
a9e1c058 2746
a8f3a651 2747 $ra->id = $DB->insert_record('role_assignments', $ra);
a9e1c058 2748
2749 } else { // We already have one, just update it
96608a55 2750 $ra->id = $ra->id;
96608a55 2751 $ra->enrol = $enrol;
c421ad4b 2752 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
0616d3e8 2753 /// by repeating queries with the same exact parameters in a 100 secs time window
96608a55 2754 $ra->timestart = round($timestart, -2);
2755 $ra->timeend = $timeend;
2756 $ra->timemodified = $timemodified;
2757 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
a9e1c058 2758
bb4b6010 2759 $DB->update_record('role_assignments', $ra);
9ebcb4d2 2760 }
2761
96608a55 2762/// mark context as dirty - modules might use has_capability() in xxx_role_assing()
2763/// again expensive, but needed
2764 mark_context_dirty($context->path);
128f0984 2765
96608a55 2766 if (!empty($USER->id) && $USER->id == $userid) {
2767/// If the user is the current user, then do full reload of capabilities too.
2768 load_all_capabilities();
2769 }
c421ad4b 2770
96608a55 2771/// Ask all the modules if anything needs to be done for this user
17da2e6f 2772 $mods = get_plugin_list('mod');
2773 foreach ($mods as $mod => $moddir) {
2774 include_once($moddir.'/lib.php');
2775 $functionname = $mod.'_role_assign';
2776 if (function_exists($functionname)) {
2777 $functionname($userid, $context, $roleid);
0f161e1f 2778 }
a9e1c058 2779 }
eef868d1 2780
4e5f3064 2781 /// now handle metacourse role assignments if in course context
96608a55 2782 if ($context->contextlevel == CONTEXT_COURSE) {
f33e1ed4 2783 if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
4e5f3064 2784 foreach ($parents as $parent) {
1aad4310 2785 sync_metacourse($parent->parent_course);
4e5f3064 2786 }
2787 }
2788 }
6eb4f823 2789
96608a55 2790 events_trigger('role_assigned', $ra);
2791
386c151e 2792 return $ra->id;
bbbf2d40 2793}
2794
2795
2796/**
1dc1f037 2797 * Deletes one or more role assignments. You must specify at least one parameter.
cc3edaa4 2798 *
2799 * @global object
2800 * @global object
2801 * @global object
46808d7c 2802 * @param int $roleid defaults to 0
2803 * @param int $userid defaults to 0
2804 * @param int $groupid defaults to 0
2805 * @param int $contextid defaults to 0
2806 * @param mixed $enrol unassign only if enrolment type matches, NULL means anything. Defaults to NULL
2807 * @return boolean success or failure
bbbf2d40 2808 */
6bc1e5d5 2809function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NULL) {
d74067e8 2810
5a4e7398 2811 global $USER, $CFG, $DB;
11dca3ee 2812 require_once($CFG->dirroot.'/group/lib.php');
eef868d1 2813
1dc1f037 2814 $args = array('roleid', 'userid', 'groupid', 'contextid');
2815 $select = array();
5a4e7398 2816 $params = array();
2817
1dc1f037 2818 foreach ($args as $arg) {
2819 if ($$arg) {
5a4e7398 2820 $select[] = "$arg = ?";
2821 $params[] = $$arg;
1dc1f037 2822 }
2823 }
6bc1e5d5 2824 if (!empty($enrol)) {
5a4e7398 2825 $select[] = "enrol=?";
2826 $params[] = $enrol;
6bc1e5d5 2827 }
d74067e8 2828
1dc1f037 2829 if ($select) {
5a4e7398 2830 if ($ras = $DB->get_records_select('role_assignments', implode(' AND ', $select), $params)) {
17da2e6f 2831 $mods = get_plugin_list('mod');
4e5f3064 2832 foreach($ras as $ra) {
86e2c51d 2833 /// infinite loop protection when deleting recursively
5a4e7398 2834 if (!$ra = $DB->get_record('role_assignments', array('id'=>$ra->id))) {
86e2c51d 2835 continue;
2836 }
4f0c2d00 2837 $DB->delete_records('role_assignments', array('id'=>$ra->id));
86e2c51d 2838
128f0984 2839 if (!$context = get_context_instance_by_id($ra->contextid)) {
2840 // strange error, not much to do
2841 continue;
2842 }
2843
2844 /* mark contexts as dirty here, because we need the refreshed
2845 * caps bellow to delete group membership and user_lastaccess!
2846 * and yes, this is very expensive for bulk operations :-(
2847 */
2848 mark_context_dirty($context->path);
2849
2850 /// If the user is the current user, then do full reload of capabilities too.
4e5f3064 2851 if (!empty($USER->id) && $USER->id == $ra->userid) {
2f1a4248 2852 load_all_capabilities();
4e5f3064 2853 }
0f161e1f 2854
2855 /// Ask all the modules if anything needs to be done for this user
17da2e6f 2856 foreach ($mods as $mod=>$moddir) {
2857 include_once($moddir.'/lib.php');
4e5f3064 2858 $functionname = $mod.'_role_unassign';
2859 if (function_exists($functionname)) {
2860 $functionname($ra->userid, $context); // watch out, $context might be NULL if something goes wrong
2861 }
2862 }
2863
4f0c2d00
PS
2864 /// now handle metacourse role unassigment and removing from goups if in course context
2865 if ($context->contextlevel == CONTEXT_COURSE) {
2866
2867 // cleanup leftover course groups/subscriptions etc when user has
2868 // no capability to view course
2869 // this may be slow, but this is the proper way of doing it
2870 if (!has_capability('moodle/course:participate', $context, $ra->userid)) {
2871 // remove from groups
2872 groups_delete_group_members($context->instanceid, $ra->userid);
2873
2874 // delete lastaccess records
2875 $DB->delete_records('user_lastaccess', array('userid'=>$ra->userid, 'courseid'=>$context->instanceid));
2876 }
2877
2878 //unassign roles in metacourses if needed
2879 if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
2880 foreach ($parents as $parent) {
2881 sync_metacourse($parent->parent_course);
2882 }
2883 }
2884 }
2885
2886 events_trigger('role_unassigned', $ra);
2887 }
2888 }
2889 }
2890
2891 return true;
2892}
2893
2894/**
2895 * Enrol someone without using the default role in a course
2896 *
2897 * A convenience function to take care of the common case where you
2898 * just want to enrol someone using the default role into a course
2899 *
2900 * @param object $course
2901 * @param object $user
2902 * @param string $enrol the plugin used to do this enrolment
2903 * @return bool
2904 */
2905function enrol_into_course($course, $user, $enrol) {
2906
2907 $timestart = time();
2908 // remove time part from the timestamp and keep only the date part
2909 $timestart = make_timestamp(date('Y', $timestart), date('m', $timestart), date('d', $timestart), 0, 0, 0);
2910 if ($course->enrolperiod) {
2911 $timeend = $timestart + $course->enrolperiod;
2912 } else {
2913 $timeend = 0;
2914 }
2915
2916 if ($role = get_default_course_role($course)) {
2917
2918 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2919
2920 if (!role_assign($role->id, $user->id, 0, $context->id, $timestart, $timeend, 0, $enrol)) {
2921 return false;
2922 }
2923
2924 // force accessdata refresh for users visiting this context...
2925 mark_context_dirty($context->path);
2926
2927 email_welcome_message_to_user($course, $user);
2928
2929 add_to_log($course->id, 'course', 'enrol',
2930 'view.php?id='.$course->id, $course->id);
2931
2932 return true;
2933 }
2934
2935 return false;
2936}
2937
2938/**
2939 * Determines if a user is currently logged in
2940 *
2941 * @return bool
2942 */
2943function isloggedin() {
2944 global $USER;
2945
2946 return (!empty($USER->id));
2947}
2948
2949/**
2950 * Determines if a user is logged in as real guest user with username 'guest'.
2951 *
2952 * @param int $user mixed user object or id, $USER if not specified
2953 * @return bool true if user is the real guest user, false if not logged in or other user
2954 */
2955function isguestuser($user = NULL) {
2956 global $USER, $DB, $CFG;
2957
2958 // make sure we have the user id cached in config table, because we are going to use it a lot
2959 if (empty($CFG->siteguest)) {
2960 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
2961 // guest does not exist yet, weird
2962 return false;
2963 }
2964 set_config('siteguest', $guestid);
2965 }
2966 if ($user === NULL) {
2967 $user = $USER;
2968 }
2969
2970 if ($user === NULL) {
2971 // happens when setting the $USER
2972 return false;
2973
2974 } else if (is_numeric($user)) {
2975 return ($CFG->siteguest == $user);
2976
2977 } else if (is_object($user)) {
2978 if (empty($user->id)) {
2979 return false; // not logged in means is not be guest
2980 } else {
2981 return ($CFG->siteguest == $user->id);
2982 }
2983
2984 } else {
2985 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
2986 }
2987}
2988
2989/**
2990 * Does user have a (temporary or real) guest access to course?
2991 *
2992 * @param object $context
2993 * @param object|int $user
2994 * @return bool
2995 */
2996function is_guest($context, $user = NULL) {
2997 // first find the course context
2998 $coursecontext = get_course_context($context);
2999
3000 // make sure there is a real user specified
3001 if ($user === NULL) {
3002 $userid = !empty($USER->id) ? $USER->id : 0;
3003 } else {
3004 $userid = !empty($user->id) ? $user->id : $user;
3005 }
3006
3007 if (isguestuser($userid)) {
3008 // can not inspect or be enrolled
3009 return true;
3010 }
3011
3012 if (has_capability('moodle/course:view', $coursecontext, $user)) {
3013 // viewing users appear out of nowhere, they are neither guests nor participants
3014 return false;
3015 }
3016
3017 if (has_capability('moodle/course:participate', $coursecontext, $userid, false)) {
3018 return false;
3019 }
3020
3021 return true;
3022}
3023
3024
3025/**
3026 * Returns true if user has course:inspect capability in course,
3027 * this is intended for admins, managers (aka small admins), inspectors, etc.
3028 *
3029 * @param object $context
3030 * @param int|object $user, if NULL $USER is used
3031 * @param string $withcapability extra capability name
3032 * @return bool
3033 */
3034function is_viewing($context, $user = NULL, $withcapability = '') {
3035 global $USER;
3036
3037 // first find the course context
3038 $coursecontext = get_course_context($context);
3039
3040 if (isguestuser($user)) {
3041 // can not inspect
3042 return true;
3043 }
3044
3045 if (!has_capability('moodle/course:view', $coursecontext, $user)) {
3046 // admins are allowed to inspect courses
3047 return false;
3048 }
3049
3050 if ($withcapability and !has_capability($withcapability, $context, $user)) {
3051 // site admins always have the capability, but the enrolment above blocks
3052 return false;
3053 }
3054
3055 return true;
3056}
3057
3058/**
3059 * Returns true if user is enrolled (is participating) in course
3060 * this is intended for students and teachers.
3061 *
3062 * @param object $context
3063 * @param int|object $user, if NULL $USER is used, oherwise user object or id expected
3064 * @param string $withcapability extra capability name
3065 * @return bool
3066 */
3067function is_enrolled($context, $user = NULL, $withcapability = '') {
3068 global $USER;
3069
3070 // first find the course context
3071 $coursecontext = get_course_context($context);
3072
3073 // make sure there is a real user specified
3074 if ($user === NULL) {
3075 $userid = !empty($USER->id) ? $USER->id : 0;
3076 } else {
3077 $userid = !empty($user->id) ? $user->id : $user;
3078 }
3079
3080 if (empty($userid)) {
3081 // not-logged-in!
3082 return false;
3083 } else if (isguestuser($userid)) {
3084 // guest account can not be enrolled anywhere
3085 return false;
3086 }
3087
3088 if ($coursecontext->instanceid != SITEID and !has_capability('moodle/course:participate', $coursecontext, $userid, false)) {
3089 // admins are not enrolled, everybody is "enrolled" in the frontpage course
3090 return false;
3091 }
3092
3093 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
3094 return false;
3095 }
3096
3097 return true;
3098}
3099
3100/**
3101 * Returns array with sql code and parameters returning all ids
3102 * of users enrolled into course.
3103 * @param object $context
3104 * @param string $withcapability
3105 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3106 * @param string $prefix used for alias of user table, parameter names and in aliases of other used tables
3107 * @return array list($sql, $params)
3108 */
3109function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $prefix = 'eu') {
3110 global $DB;
3111
3112 if ($context->contextlevel < CONTEXT_COURSE) {
3113 throw new coding_exception('get_enrolled_sql() expects course context and bellow!');
3114 }
3115
3116 // first find the course context
3117 if ($context->contextlevel == CONTEXT_COURSE) {
3118 $coursecontext = $context;
3119
3120 } else if ($context->contextlevel == CONTEXT_MODULE) {
3121 $coursecontext = get_context_instance_by_id(get_parent_contextid($context, MUST_EXIST));
3122
3123 } else if ($context->contextlevel == CONTEXT_BLOCK) {
3124 $parentcontext = get_context_instance_by_id(get_parent_contextid($context, MUST_EXIST));
3125 if ($parentcontext->contextlevel == CONTEXT_COURSE) {
3126 $coursecontext = $parentcontext;
3127 } else if ($parentcontext->contextlevel == CONTEXT_MODULE) {
3128 $coursecontext = get_context_instance_by_id(get_parent_contextid($parentcontext, MUST_EXIST));
3129 } else {
3130 throw new coding_exception('Invalid context supplied to get_enrolled_sql()!');
3131 }
3132
3133 } else {
3134 throw new coding_exception('Invalid context supplied to get_enrolled_sql()!');
3135 }
3136
3137 list($contextids, $contextpaths) = get_context_info_list($context);
3138 list($coursecontextids, $coursecontextpaths) = get_context_info_list($coursecontext);
3139
3140 // get all relevant capability info for all roles
3141 if ($withcapability) {
3142 list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con00');
3143 $incaps = "IN (:participate, :withcap)";
3144 $params['participate'] = 'moodle/course:participate';
3145 $params['withcap'] = $withcapability;
3146 } else {
3147 list($incontexts, $params) = $DB->get_in_or_equal($coursecontextids, SQL_PARAMS_NAMED, 'con00');
3148 $incaps = "= :participate";
3149 $params['participate'] = 'moodle/course:participate';
3150 }
3151 $defs = array();
3152 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3153 FROM {role_capabilities} rc
3154 JOIN {context} ctx on rc.contextid = ctx.id
3155 WHERE rc.contextid $incontexts AND rc.capability $incaps";
3156 $rcs = $DB->get_records_sql($sql, $params);
3157 foreach ($rcs as $rc) {
3158 $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3159 }
3160
3161 $courseaccess = array();
3162 if (!empty($defs['moodle/course:participate'])) {
3163 foreach ($coursecontextpaths as $path) {
3164 if (empty($defs['moodle/course:participate'][$path])) {
3165 continue;
3166 }
3167
3168 foreach($defs['moodle/course:participate'][$path] as $roleid => $perm) {