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