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