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