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