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