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