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