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