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