3 ///////////////////////////////////////////////////////////////////////////
5 // NOTICE OF COPYRIGHT //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.org //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
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. //
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: //
22 // http://www.gnu.org/copyleft/gpl.html //
24 ///////////////////////////////////////////////////////////////////////////
27 * Public API vs internals
28 * -----------------------
30 * General users probably only care about
33 * - get_context_instance()
34 * - get_context_instance_by_id()
35 * - get_parent_contexts()
36 * - get_child_contexts()
38 * Whether the user can do something...
40 * - require_capability()
41 * - require_login() (from moodlelib)
43 * What courses has this user access to?
44 * - get_user_courses_bycap()
46 * What users can do X in this context?
47 * - get_users_by_capability()
50 * - enrol_into_course()
51 * - role_assign()/role_unassign()
55 * - load_all_capabilities()
56 * - reload_all_capabilities()
58 * - has_capability_in_accessdata()
60 * - get_user_access_sitewide()
62 * - get_role_access_bycontext()
67 * - "ctx" means context
72 * Access control data is held in the "accessdata" array
73 * which - for the logged-in user, will be in $USER->access
75 * For other users can be generated and passed around (but see
76 * the $ACCESS global).
78 * $accessdata is a multidimensional array, holding
79 * role assignments (RAs), role-capabilities-perm sets
80 * (role defs) and a list of courses we have loaded
83 * Things are keyed on "contextpaths" (the path field of
84 * the context table) for fast walking up/down the tree.
86 * $accessdata[ra][$contextpath]= array($roleid)
87 * [$contextpath]= array($roleid)
88 * [$contextpath]= array($roleid)
90 * Role definitions are stored like this
91 * (no cap merge is done - so it's compact)
93 * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
94 * [mod/forum:editallpost] = -1
95 * [mod/forum:startdiscussion] = -1000
97 * See how has_capability_in_accessdata() walks up/down the tree.
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
105 * $accessdata[loaded] = array($contextpath, $contextpath)
110 * For the logged-in user, accessdata is long-lived.
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.
116 * Changes at the sytem level will force the reload for everyone.
120 * The default role assignment is not in the DB, so we
121 * add it manually to accessdata.
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.
129 require_once $CFG->dirroot
.'/lib/blocklib.php';
131 // permission definitions
132 define('CAP_INHERIT', 0);
133 define('CAP_ALLOW', 1);
134 define('CAP_PREVENT', -1);
135 define('CAP_PROHIBIT', -1000);
137 // context definitions
138 define('CONTEXT_SYSTEM', 10);
139 define('CONTEXT_USER', 30);
140 define('CONTEXT_COURSECAT', 40);
141 define('CONTEXT_COURSE', 50);
142 define('CONTEXT_GROUP', 60);
143 define('CONTEXT_MODULE', 70);
144 define('CONTEXT_BLOCK', 80);
146 // capability risks - see http://docs.moodle.org/en/Development:Hardening_new_Roles_system
147 define('RISK_MANAGETRUST', 0x0001);
148 define('RISK_CONFIG', 0x0002);
149 define('RISK_XSS', 0x0004);
150 define('RISK_PERSONAL', 0x0008);
151 define('RISK_SPAM', 0x0010);
152 define('RISK_DATALOSS', 0x0020);
155 define('ROLENAME_ORIGINAL', 0);// the name as defined in the role definition
156 define('ROLENAME_ALIAS', 1); // the name as defined by a role alias
157 define('ROLENAME_BOTH', 2); // Both, like this: Role alias (Original)
159 require_once($CFG->dirroot
.'/group/lib.php');
161 $context_cache = array(); // Cache of all used context objects for performance (by level and instance)
162 $context_cache_id = array(); // Index to above cache by id
164 $DIRTYCONTEXTS = null; // dirty contexts cache
165 $ACCESS = array(); // cache of caps for cron user switching and has_capability for other users (==not $USER)
166 $RDEFS = array(); // role definitions cache - helps a lot with mem usage in cron
168 function get_role_context_caps($roleid, $context) {
169 //this is really slow!!!! - do not use above course context level!
171 $result[$context->id
] = array();
173 // first emulate the parent context capabilities merging into context
174 $searchcontexts = array_reverse(get_parent_contexts($context));
175 array_push($searchcontexts, $context->id
);
176 foreach ($searchcontexts as $cid) {
177 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
178 foreach ($capabilities as $cap) {
179 if (!array_key_exists($cap->capability
, $result[$context->id
])) {
180 $result[$context->id
][$cap->capability
] = 0;
182 $result[$context->id
][$cap->capability
] +
= $cap->permission
;
187 // now go through the contexts bellow given context
188 $searchcontexts = array_keys(get_child_contexts($context));
189 foreach ($searchcontexts as $cid) {
190 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
191 foreach ($capabilities as $cap) {
192 if (!array_key_exists($cap->contextid
, $result)) {
193 $result[$cap->contextid
] = array();
195 $result[$cap->contextid
][$cap->capability
] = $cap->permission
;
204 * Gets the accessdata for role "sitewide"
205 * (system down to course)
209 function get_role_access($roleid, $accessdata=NULL) {
213 /* Get it in 1 cheap DB query...
214 * - relevant role caps at the root and down
215 * to the course level - but not below
217 if (is_null($accessdata)) {
218 $accessdata = array(); // named list
219 $accessdata['ra'] = array();
220 $accessdata['rdef'] = array();
221 $accessdata['loaded'] = array();
225 // Overrides for the role IN ANY CONTEXTS
226 // down to COURSE - not below -
228 $sql = "SELECT ctx.path,
229 rc.capability, rc.permission
230 FROM {$CFG->prefix}context ctx
231 JOIN {$CFG->prefix}role_capabilities rc
232 ON rc.contextid=ctx.id
233 WHERE rc.roleid = {$roleid}
234 AND ctx.contextlevel <= ".CONTEXT_COURSE
."
235 ORDER BY ctx.depth, ctx.path";
237 // we need extra caching in cron only
238 if (defined('FULLME') and FULLME
=== 'cron') {
239 static $cron_cache = array();
241 if (!isset($cron_cache[$roleid])) {
242 $cron_cache[$roleid] = array();
243 if ($rs = get_recordset_sql($sql)) {
244 while ($rd = rs_fetch_next_record($rs)) {
245 $cron_cache[$roleid][] = $rd;
251 foreach ($cron_cache[$roleid] as $rd) {
252 $k = "{$rd->path}:{$roleid}";
253 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
257 if ($rs = get_recordset_sql($sql)) {
258 while ($rd = rs_fetch_next_record($rs)) {
259 $k = "{$rd->path}:{$roleid}";
260 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
271 * Gets the accessdata for role "sitewide"
272 * (system down to course)
276 function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
280 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
281 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
284 // Overrides for the role in any contexts related to the course
286 $sql = "SELECT ctx.path,
287 rc.capability, rc.permission
288 FROM {$CFG->prefix}context ctx
289 JOIN {$CFG->prefix}role_capabilities rc
290 ON rc.contextid=ctx.id
291 WHERE rc.roleid = {$roleid}
292 AND (ctx.id = ".SYSCONTEXTID
." OR ctx.path LIKE '$base/%')
293 AND ctx.contextlevel <= ".CONTEXT_COURSE
."
294 ORDER BY ctx.depth, ctx.path";
296 if ($rs = get_recordset_sql($sql)) {
297 while ($rd = rs_fetch_next_record($rs)) {
298 $k = "{$rd->path}:{$roleid}";
299 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
310 * Get the default guest role
311 * @return object role
313 function get_guest_role() {
316 if (empty($CFG->guestroleid
)) {
317 if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW
)) {
318 $guestrole = array_shift($roles); // Pick the first one
319 set_config('guestroleid', $guestrole->id
);
322 debugging('Can not find any guest role!');
326 if ($guestrole = get_record('role','id', $CFG->guestroleid
)) {
329 //somebody is messing with guest roles, remove incorrect setting and try to find a new one
330 set_config('guestroleid', '');
331 return get_guest_role();
337 * This function returns whether the current user has the capability of performing a function
338 * For example, we can do has_capability('mod/forum:replypost',$context) in forum
339 * @param string $capability - name of the capability (or debugcache or clearcache)
340 * @param object $context - a context object (record from context table)
341 * @param integer $userid - a userid number, empty if current $USER
342 * @param bool $doanything - if false, ignore do anything
345 function has_capability($capability, $context, $userid=NULL, $doanything=true) {
346 global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS;
348 // the original $CONTEXT here was hiding serious errors
349 // for security reasons do not reuse previous context
350 if (empty($context)) {
351 debugging('Incorrect context specified');
355 /// Some sanity checks
356 if (debugging('',DEBUG_DEVELOPER
)) {
357 static $capsnames = null; // one request per page only
359 if (is_null($capsnames)) {
360 if ($caps = get_records('capabilities', '', '', '', 'id, name')) {
361 $capsnames = array();
362 foreach ($caps as $cap) {
363 $capsnames[$cap->name
] = true;
367 if ($capsnames) { // ignore if can not fetch caps
368 if (!isset($capsnames[$capability])) {
369 debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
372 if (!is_bool($doanything)) {
373 debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.');
377 if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid
381 if (is_null($context->path
) or $context->depth
== 0) {
382 //this should not happen
383 $contexts = array(SYSCONTEXTID
, $context->id
);
384 $context->path
= '/'.SYSCONTEXTID
.'/'.$context->id
;
385 debugging('Context id '.$context->id
.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER
);
388 $contexts = explode('/', $context->path
);
389 array_shift($contexts);
392 if (defined('FULLME') && FULLME
=== 'cron' && !isset($USER->access
)) {
393 // In cron, some modules setup a 'fake' $USER,
394 // ensure we load the appropriate accessdata.
395 if (isset($ACCESS[$userid])) {
396 $DIRTYCONTEXTS = NULL; //load fresh dirty contexts
398 load_user_accessdata($userid);
399 $DIRTYCONTEXTS = array();
401 $USER->access
= $ACCESS[$userid];
403 } else if ($USER->id
== $userid && !isset($USER->access
)) {
404 // caps not loaded yet - better to load them to keep BC with 1.8
405 // not-logged-in user or $USER object set up manually first time here
406 load_all_capabilities();
407 $ACCESS = array(); // reset the cache for other users too, the dirty contexts are empty now
411 // Load dirty contexts list if needed
412 if (!isset($DIRTYCONTEXTS)) {
413 if (isset($USER->access
['time'])) {
414 $DIRTYCONTEXTS = get_dirty_contexts($USER->access
['time']);
417 $DIRTYCONTEXTS = array();
421 // Careful check for staleness...
422 if (count($DIRTYCONTEXTS) !== 0 and is_contextpath_dirty($contexts, $DIRTYCONTEXTS)) {
423 // reload all capabilities - preserving loginas, roleswitches, etc
424 // and then cleanup any marks of dirtyness... at least from our short
429 if (defined('FULLME') && FULLME
=== 'cron') {
430 load_user_accessdata($userid);
431 $USER->access
= $ACCESS[$userid];
432 $DIRTYCONTEXTS = array();
435 reload_all_capabilities();
439 // divulge how many times we are called
440 //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
442 if ($USER->id
== $userid) { // we must accept strings and integers in $userid
444 // For the logged in user, we have $USER->access
445 // which will have all RAs and caps preloaded for
446 // course and above contexts.
448 // Contexts below courses && contexts that do not
449 // hang from courses are loaded into $USER->access
450 // on demand, and listed in $USER->access[loaded]
452 if ($context->contextlevel
<= CONTEXT_COURSE
) {
453 // Course and above are always preloaded
454 return has_capability_in_accessdata($capability, $context, $USER->access
, $doanything);
456 // Load accessdata for below-the-course contexts
457 if (!path_inaccessdata($context->path
,$USER->access
)) {
458 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
459 // $bt = debug_backtrace();
460 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
461 load_subcontext($USER->id
, $context, $USER->access
);
463 return has_capability_in_accessdata($capability, $context, $USER->access
, $doanything);
466 if (!isset($ACCESS[$userid])) {
467 load_user_accessdata($userid);
469 if ($context->contextlevel
<= CONTEXT_COURSE
) {
470 // Course and above are always preloaded
471 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
473 // Load accessdata for below-the-course contexts as needed
474 if (!path_inaccessdata($context->path
, $ACCESS[$userid])) {
475 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
476 // $bt = debug_backtrace();
477 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
478 load_subcontext($userid, $context, $ACCESS[$userid]);
480 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
484 * This function returns whether the current user has any of the capabilities in the
485 * $capabilities array. This is a simple wrapper around has_capability for convinience.
487 * There are probably tricks that could be done to improve the performance here, for example,
488 * check the capabilities that are already cached first.
490 * @param array $capabilities - an array of capability names.
491 * @param object $context - a context object (record from context table)
492 * @param integer $userid - a userid number, empty if current $USER
493 * @param bool $doanything - if false, ignore do anything
496 function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
497 foreach ($capabilities as $capability) {
498 if (has_capability($capability, $context, $userid, $doanything)) {
506 * Uses 1 DB query to answer whether a user is an admin at the sitelevel.
507 * It depends on DB schema >=1.7 but does not depend on the new datastructures
508 * in v1.9 (context.path, or $USER->access)
510 * Will return true if the userid has any of
511 * - moodle/site:config
512 * - moodle/legacy:admin
513 * - moodle/site:doanything
516 * @returns bool $isadmin
518 function is_siteadmin($userid) {
521 $sql = "SELECT SUM(rc.permission)
522 FROM " . $CFG->prefix
. "role_capabilities rc
523 JOIN " . $CFG->prefix
. "context ctx
524 ON ctx.id=rc.contextid
525 JOIN " . $CFG->prefix
. "role_assignments ra
526 ON ra.roleid=rc.roleid AND ra.contextid=ctx.id
527 WHERE ctx.contextlevel=10
528 AND ra.userid={$userid}
529 AND rc.capability IN ('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything')
530 GROUP BY rc.capability
531 HAVING SUM(rc.permission) > 0";
533 $isadmin = record_exists_sql($sql);
537 function get_course_from_path ($path) {
538 // assume that nothing is more than 1 course deep
539 if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
545 function path_inaccessdata($path, $accessdata) {
547 // assume that contexts hang from sys or from a course
548 // this will only work well with stuff that hangs from a course
549 if (in_array($path, $accessdata['loaded'], true)) {
550 // error_log("found it!");
553 $base = '/' . SYSCONTEXTID
;
554 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
556 if ($path === $base) {
559 if (in_array($path, $accessdata['loaded'], true)) {
567 * Walk the accessdata array and return true/false.
568 * Deals with prohibits, roleswitching, aggregating
571 * The main feature of here is being FAST and with no
576 * Switch Roles exits early
577 * -----------------------
578 * cap checks within a switchrole need to exit early
579 * in our bottom up processing so they don't "see" that
580 * there are real RAs that can do all sorts of things.
582 * Switch Role merges with default role
583 * ------------------------------------
584 * If you are a teacher in course X, you have at least
585 * teacher-in-X + defaultloggedinuser-sitewide. So in the
586 * course you'll have techer+defaultloggedinuser.
587 * We try to mimic that in switchrole.
589 * Local-most role definition and role-assignment wins
590 * ---------------------------------------------------
591 * So if the local context has said 'allow', it wins
592 * over a high-level context that says 'deny'.
593 * This is applied when walking rdefs, and RAs.
594 * Only at the same context the values are SUM()med.
596 * The exception is CAP_PROHIBIT.
598 * "Guest default role" exception
599 * ------------------------------
601 * See MDL-7513 and $ignoreguest below for details.
605 * IF we are being asked about moodle/legacy:guest
606 * OR moodle/course:view
607 * FOR a real, logged-in user
608 * AND we reached the top of the path in ra and rdef
609 * AND that role has moodle/legacy:guest === 1...
610 * THEN we act as if we hadn't seen it.
615 * - Document how it works
616 * - Rewrite in ASM :-)
619 function has_capability_in_accessdata($capability, $context, $accessdata, $doanything) {
623 $path = $context->path
;
625 // build $contexts as a list of "paths" of the current
626 // contexts and parents with the order top-to-bottom
627 $contexts = array($path);
628 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
630 array_unshift($contexts, $path);
633 $ignoreguest = false;
634 if (isset($accessdata['dr'])
635 && ($capability == 'moodle/course:view'
636 ||
$capability == 'moodle/legacy:guest')) {
637 // At the base, ignore rdefs where moodle/legacy:guest
639 $ignoreguest = $accessdata['dr'];
642 // Coerce it to an int
643 $CAP_PROHIBIT = (int)CAP_PROHIBIT
;
645 $cc = count($contexts);
651 // role-switches loop
653 if (isset($accessdata['rsw'])) {
654 // check for isset() is fast
655 // empty() is slow...
656 if (empty($accessdata['rsw'])) {
657 unset($accessdata['rsw']); // keep things fast and unambiguous
660 // From the bottom up...
661 for ($n=$cc-1;$n>=0;$n--) {
662 $ctxp = $contexts[$n];
663 if (isset($accessdata['rsw'][$ctxp])) {
664 // Found a switchrole assignment
665 // check for that role _plus_ the default user role
666 $ras = array($accessdata['rsw'][$ctxp],$CFG->defaultuserroleid
);
667 for ($rn=0;$rn<2;$rn++
) {
668 $roleid = (int)$ras[$rn];
669 // Walk the path for capabilities
670 // from the bottom up...
671 for ($m=$cc-1;$m>=0;$m--) {
672 $capctxp = $contexts[$m];
673 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
674 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
676 // The most local permission (first to set) wins
677 // the only exception is CAP_PROHIBIT
680 } elseif ($perm === $CAP_PROHIBIT) {
687 // As we are dealing with a switchrole,
688 // we return _here_, do _not_ walk up
689 // the hierarchy any further
692 // didn't find it as an explicit cap,
693 // but maybe the user candoanything in this context...
694 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
707 // Main loop for normal RAs
708 // From the bottom up...
710 for ($n=$cc-1;$n>=0;$n--) {
711 $ctxp = $contexts[$n];
712 if (isset($accessdata['ra'][$ctxp])) {
713 // Found role assignments on this leaf
714 $ras = $accessdata['ra'][$ctxp];
719 for ($rn=0;$rn<$rc;$rn++
) {
720 $roleid = (int)$ras[$rn];
723 // Walk the path for capabilities
724 // from the bottom up...
725 for ($m=$cc-1;$m>=0;$m--) {
726 $capctxp = $contexts[$m];
727 // ignore some guest caps
728 // at base ra and rdef
729 if ($ignoreguest == $roleid
732 && isset($accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'])
733 && $accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'] > 0) {
736 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
737 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
738 // The most local permission (first to set) wins
739 // the only exception is CAP_PROHIBIT
740 if ($rolecan === 0) {
743 } elseif ($perm === $CAP_PROHIBIT) {
750 // Rules for RAs at the same context...
751 // - prohibits always wins
752 // - permissions at the same ctxlevel & capdepth are added together
753 // - deeper capdepth wins
754 if ($ctxcan === $CAP_PROHIBIT ||
$rolecan === $CAP_PROHIBIT) {
755 $ctxcan = $CAP_PROHIBIT;
757 } elseif ($ctxcapdepth === $rolecapdepth) {
759 } elseif ($ctxcapdepth < $rolecapdepth) {
761 $ctxcapdepth = $rolecapdepth;
762 } else { // ctxcaptdepth is deeper
766 // The most local RAs with a defined
767 // permission ($ctxcan) win, except
769 // NOTE: If we want the deepest RDEF to
770 // win regardless of the depth of the RA,
771 // change the elseif below to read
772 // ($can === 0 || $capdepth < $ctxcapdepth) {
773 if ($ctxcan === $CAP_PROHIBIT) {
776 } elseif ($can === 0) { // see note above
778 $capdepth = $ctxcapdepth;
785 // didn't find it as an explicit cap,
786 // but maybe the user candoanything in this context...
787 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
797 function aggregate_roles_from_accessdata($context, $accessdata) {
799 $path = $context->path
;
801 // build $contexts as a list of "paths" of the current
802 // contexts and parents with the order top-to-bottom
803 $contexts = array($path);
804 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
806 array_unshift($contexts, $path);
809 $cc = count($contexts);
812 // From the bottom up...
813 for ($n=$cc-1;$n>=0;$n--) {
814 $ctxp = $contexts[$n];
815 if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
816 // Found assignments on this leaf
817 $addroles = $accessdata['ra'][$ctxp];
818 $roles = array_merge($roles, $addroles);
822 return array_unique($roles);
826 * This is an easy to use function, combining has_capability() with require_course_login().
827 * And will call those where needed.
829 * It checks for a capability assertion being true. If it isn't
830 * then the page is terminated neatly with a standard error message.
832 * If the user is not logged in, or is using 'guest' access or other special "users,
833 * it provides a logon prompt.
835 * @param string $capability - name of the capability
836 * @param object $context - a context object (record from context table)
837 * @param integer $userid - a userid number
838 * @param bool $doanything - if false, ignore do anything
839 * @param string $errorstring - an errorstring
840 * @param string $stringfile - which stringfile to get it from
842 function require_capability($capability, $context, $userid=NULL, $doanything=true,
843 $errormessage='nopermissions', $stringfile='') {
847 /* Empty $userid means current user, if the current user is not logged in,
848 * then make sure they are (if needed).
849 * Originally there was a check for loaded permissions - it is not needed here.
850 * Context is now required parameter, the cached $CONTEXT was only hiding errors.
854 if (empty($userid)) {
855 if ($context->contextlevel
== CONTEXT_COURSE
) {
856 require_login($context->instanceid
);
858 } else if ($context->contextlevel
== CONTEXT_MODULE
) {
859 if (!$cm = get_record('course_modules', 'id', $context->instanceid
)) {
860 error('Incorrect module');
862 if (!$course = get_record('course', 'id', $cm->course
)) {
863 error('Incorrect course.');
865 require_course_login($course, true, $cm);
866 $errorlink = $CFG->wwwroot
.'/course/view.php?id='.$cm->course
;
868 } else if ($context->contextlevel
== CONTEXT_SYSTEM
) {
869 if (!empty($CFG->forcelogin
)) {
878 /// OK, if they still don't have the capability then print a nice error message
880 if (!has_capability($capability, $context, $userid, $doanything)) {
881 $capabilityname = get_capability_string($capability);
882 print_error($errormessage, $stringfile, $errorlink, $capabilityname);
887 * Get an array of courses (with magic extra bits)
888 * where the accessdata and in DB enrolments show
889 * that the cap requested is available.
891 * The main use is for get_my_courses().
895 * - $fields is an array of fieldnames to ADD
896 * so name the fields you really need, which will
897 * be added and uniq'd
899 * - the course records have $c->context which is a fully
900 * valid context object. Saves you a query per course!
902 * - the course records have $c->categorypath to make
903 * category lookups cheap
905 * - current implementation is split in -
907 * - if the user has the cap systemwide, stupidly
908 * grab *every* course for a capcheck. This eats
909 * a TON of bandwidth, specially on large sites
910 * with separate DBs...
912 * - otherwise, fetch "likely" courses with a wide net
913 * that should get us _cheaply_ at least the courses we need, and some
914 * we won't - we get courses that...
915 * - are in a category where user has the cap
916 * - or where use has a role-assignment (any kind)
917 * - or where the course has an override on for this cap
919 * - walk the courses recordset checking the caps oneach one
920 * the checks are all in memory and quite fast
921 * (though we could implement a specialised variant of the
922 * has_capability_in_accessdata() code to speed it up)
924 * @param string $capability - name of the capability
925 * @param array $accessdata - accessdata session array
926 * @param bool $doanything - if false, ignore do anything
927 * @param string $sort - sorting fields - prefix each fieldname with "c."
928 * @param array $fields - additional fields you are interested in...
929 * @param int $limit - set if you want to limit the number of courses
930 * @return array $courses - ordered array of course objects - see notes above
933 function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) {
937 // Slim base fields, let callers ask for what they need...
938 $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
940 if (!is_null($fields)) {
941 $fields = array_merge($basefields, $fields);
942 $fields = array_unique($fields);
944 $fields = $basefields;
946 $coursefields = 'c.' .implode(',c.', $fields);
950 $sort = "ORDER BY $sort";
953 $sysctx = get_context_instance(CONTEXT_SYSTEM
);
954 if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
956 // Apparently the user has the cap sitewide, so walk *every* course
957 // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
960 $sql = "SELECT $coursefields,
961 ctx.id AS ctxid, ctx.path AS ctxpath,
962 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
963 cc.path AS categorypath
964 FROM {$CFG->prefix}course c
965 JOIN {$CFG->prefix}course_categories cc
967 JOIN {$CFG->prefix}context ctx
968 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
970 $rs = get_recordset_sql($sql);
973 // narrow down where we have the caps to a few contexts
974 // this will be a combination of
975 // - courses where user has an explicit enrolment
976 // - courses that have an override (any status) on that capability
977 // - categories where user has the rights (granted status) on that capability
980 FROM {$CFG->prefix}context ctx
981 WHERE ctx.contextlevel=".CONTEXT_COURSECAT
."
983 $rs = get_recordset_sql($sql);
985 while ($catctx = rs_fetch_next_record($rs)) {
986 if ($catctx->path
!= ''
987 && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
988 $catpaths[] = $catctx->path
;
993 if (count($catpaths)) {
994 $cc = count($catpaths);
995 for ($n=0;$n<$cc;$n++
) {
996 $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
998 $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')';
1004 $capany = " OR rc.capability='moodle/site:doanything'";
1007 /// UNION 3 queries:
1008 /// - user role assignments in courses
1009 /// - user capability (override - any status) in courses
1010 /// - user right (granted status) in categories (optionally executed)
1011 /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
1012 /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
1014 SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath
1017 ctx.id AS ctxid, ctx.path AS ctxpath,
1018 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1019 cc.path AS categorypath
1020 FROM {$CFG->prefix}course c
1021 JOIN {$CFG->prefix}course_categories cc
1023 JOIN {$CFG->prefix}context ctx
1024 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1025 JOIN {$CFG->prefix}role_assignments ra
1026 ON (ra.contextid=ctx.id AND ra.userid=$userid)
1029 ctx.id AS ctxid, ctx.path AS ctxpath,
1030 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1031 cc.path AS categorypath
1032 FROM {$CFG->prefix}course c
1033 JOIN {$CFG->prefix}course_categories cc
1035 JOIN {$CFG->prefix}context ctx
1036 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1037 JOIN {$CFG->prefix}role_capabilities rc
1038 ON (rc.contextid=ctx.id AND (rc.capability='$cap' $capany)) ";
1040 if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too
1044 ctx.id AS ctxid, ctx.path AS ctxpath,
1045 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1046 cc.path AS categorypath
1047 FROM {$CFG->prefix}course c
1048 JOIN {$CFG->prefix}course_categories cc
1050 JOIN {$CFG->prefix}context ctx
1051 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1055 /// Close the inline_view and join with courses table to get requested $coursefields
1058 INNER JOIN {$CFG->prefix}course c
1059 ON inline_view.id = c.id";
1061 /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
1063 " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause
1065 $rs = get_recordset_sql($sql);
1068 /// Confirm rights (granted capability) for each course returned
1070 $cc = 0; // keep count
1071 while ($c = rs_fetch_next_record($rs)) {
1072 // build the context obj
1073 $c = make_context_subobj($c);
1075 if (has_capability_in_accessdata($cap, $c->context
, $accessdata, $doanything)) {
1077 if ($limit > 0 && $cc++
> $limit) {
1088 * It will return a nested array showing role assignments
1089 * all relevant role capabilities for the user at
1090 * site/metacourse/course_category/course levels
1092 * We do _not_ delve deeper than courses because the number of
1093 * overrides at the module/block levels is HUGE.
1095 * [ra] => [/path/] = array(roleid, roleid)
1096 * [rdef] => [/path/:roleid][capability]=permission
1097 * [loaded] => array('/path', '/path')
1099 * @param $userid integer - the id of the user
1102 function get_user_access_sitewide($userid) {
1106 // this flag has not been set!
1107 // (not clean install, or upgraded successfully to 1.7 and up)
1108 if (empty($CFG->rolesactive
)) {
1112 /* Get in 3 cheap DB queries...
1113 * - role assignments - with role_caps
1114 * - relevant role caps
1115 * - above this user's RAs
1116 * - below this user's RAs - limited to course level
1119 $accessdata = array(); // named list
1120 $accessdata['ra'] = array();
1121 $accessdata['rdef'] = array();
1122 $accessdata['loaded'] = array();
1124 $sitectx = get_system_context();
1125 $base = '/'.$sitectx->id
;
1128 // Role assignments - and any rolecaps directly linked
1129 // because it's cheap to read rolecaps here over many
1132 $sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission
1133 FROM {$CFG->prefix}role_assignments ra
1134 JOIN {$CFG->prefix}context ctx
1135 ON ra.contextid=ctx.id
1136 LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc
1137 ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid)
1138 WHERE ra.userid = $userid AND ctx.contextlevel <= ".CONTEXT_COURSE
."
1139 ORDER BY ctx.depth, ctx.path, ra.roleid";
1140 $rs = get_recordset_sql($sql);
1142 // raparents collects paths & roles we need to walk up
1143 // the parenthood to build the rdef
1145 // the array will bulk up a bit with dups
1146 // which we'll later clear up
1148 $raparents = array();
1151 while ($ra = rs_fetch_next_record($rs)) {
1152 // RAs leafs are arrays to support multi
1153 // role assignments...
1154 if (!isset($accessdata['ra'][$ra->path
])) {
1155 $accessdata['ra'][$ra->path
] = array();
1157 // only add if is not a repeat caused
1158 // by capability join...
1159 // (this check is cheaper than in_array())
1160 if ($lastseen !== $ra->path
.':'.$ra->roleid
) {
1161 $lastseen = $ra->path
.':'.$ra->roleid
;
1162 array_push($accessdata['ra'][$ra->path
], $ra->roleid
);
1163 $parentids = explode('/', $ra->path
);
1164 array_shift($parentids); // drop empty leading "context"
1165 array_pop($parentids); // drop _this_ context
1167 if (isset($raparents[$ra->roleid
])) {
1168 $raparents[$ra->roleid
] = array_merge($raparents[$ra->roleid
],
1171 $raparents[$ra->roleid
] = $parentids;
1174 // Always add the roleded
1175 if (!empty($ra->capability
)) {
1176 $k = "{$ra->path}:{$ra->roleid}";
1177 $accessdata['rdef'][$k][$ra->capability
] = $ra->permission
;
1184 // Walk up the tree to grab all the roledefs
1185 // of interest to our user...
1186 // NOTE: we use a series of IN clauses here - which
1187 // might explode on huge sites with very convoluted nesting of
1188 // categories... - extremely unlikely that the number of categories
1189 // and roletypes is so large that we hit the limits of IN()
1191 foreach ($raparents as $roleid=>$contexts) {
1192 $contexts = implode(',', array_unique($contexts));
1193 if ($contexts ==! '') {
1194 $clauses[] = "(roleid=$roleid AND contextid IN ($contexts))";
1197 $clauses = implode(" OR ", $clauses);
1198 if ($clauses !== '') {
1199 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1200 FROM {$CFG->prefix}role_capabilities rc
1201 JOIN {$CFG->prefix}context ctx
1202 ON rc.contextid=ctx.id
1204 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1206 $rs = get_recordset_sql($sql);
1210 while ($rd = rs_fetch_next_record($rs)) {
1211 $k = "{$rd->path}:{$rd->roleid}";
1212 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1220 // Overrides for the role assignments IN SUBCONTEXTS
1221 // (though we still do _not_ go below the course level.
1223 // NOTE that the JOIN w sctx is with 3-way triangulation to
1224 // catch overrides to the applicable role in any subcontext, based
1225 // on the path field of the parent.
1227 $sql = "SELECT sctx.path, ra.roleid,
1228 ctx.path AS parentpath,
1229 rco.capability, rco.permission
1230 FROM {$CFG->prefix}role_assignments ra
1231 JOIN {$CFG->prefix}context ctx
1232 ON ra.contextid=ctx.id
1233 JOIN {$CFG->prefix}context sctx
1234 ON (sctx.path LIKE " . sql_concat('ctx.path',"'/%'"). " )
1235 JOIN {$CFG->prefix}role_capabilities rco
1236 ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1237 WHERE ra.userid = $userid
1238 AND sctx.contextlevel <= ".CONTEXT_COURSE
."
1239 ORDER BY sctx.depth, sctx.path, ra.roleid";
1241 $rs = get_recordset_sql($sql);
1243 while ($rd = rs_fetch_next_record($rs)) {
1244 $k = "{$rd->path}:{$rd->roleid}";
1245 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1254 * It add to the access ctrl array the data
1255 * needed by a user for a given context
1257 * @param $userid integer - the id of the user
1258 * @param $context context obj - needs path!
1259 * @param $accessdata array accessdata array
1261 function load_subcontext($userid, $context, &$accessdata) {
1267 /* Get the additional RAs and relevant rolecaps
1268 * - role assignments - with role_caps
1269 * - relevant role caps
1270 * - above this user's RAs
1271 * - below this user's RAs - limited to course level
1274 $base = "/" . SYSCONTEXTID
;
1277 // Replace $context with the target context we will
1278 // load. Normally, this will be a course context, but
1279 // may be a different top-level context.
1284 // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1285 // - BLOCK/MODULE/GROUP hanging from a course
1287 // For course contexts, we _already_ have the RAs
1288 // but the cost of re-fetching is minimal so we don't care.
1290 if ($context->contextlevel
!== CONTEXT_COURSE
1291 && $context->path
!== "$base/{$context->id}") {
1292 // Case BLOCK/MODULE/GROUP hanging from a course
1293 // Assumption: the course _must_ be our parent
1294 // If we ever see stuff nested further this needs to
1295 // change to do 1 query over the exploded path to
1296 // find out which one is the course
1297 $courses = explode('/',get_course_from_path($context->path
));
1298 $targetid = array_pop($courses);
1299 $context = get_context_instance_by_id($targetid);
1304 // Role assignments in the context and below
1306 $sql = "SELECT ctx.path, ra.roleid
1307 FROM {$CFG->prefix}role_assignments ra
1308 JOIN {$CFG->prefix}context ctx
1309 ON ra.contextid=ctx.id
1310 WHERE ra.userid = $userid
1311 AND (ctx.path = '{$context->path}' OR ctx.path LIKE '{$context->path}/%')
1312 ORDER BY ctx.depth, ctx.path, ra.roleid";
1313 $rs = get_recordset_sql($sql);
1316 // Read in the RAs, preventing duplicates
1318 $localroles = array();
1320 while ($ra = rs_fetch_next_record($rs)) {
1321 if (!isset($accessdata['ra'][$ra->path
])) {
1322 $accessdata['ra'][$ra->path
] = array();
1324 // only add if is not a repeat caused
1325 // by capability join...
1326 // (this check is cheaper than in_array())
1327 if ($lastseen !== $ra->path
.':'.$ra->roleid
) {
1328 $lastseen = $ra->path
.':'.$ra->roleid
;
1329 array_push($accessdata['ra'][$ra->path
], $ra->roleid
);
1330 array_push($localroles, $ra->roleid
);
1336 // Walk up and down the tree to grab all the roledefs
1337 // of interest to our user...
1340 // - we use IN() but the number of roles is very limited.
1342 $courseroles = aggregate_roles_from_accessdata($context, $accessdata);
1344 // Do we have any interesting "local" roles?
1345 $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1346 $wherelocalroles='';
1347 if (count($localroles)) {
1348 // Role defs for local roles in 'higher' contexts...
1349 $contexts = substr($context->path
, 1); // kill leading slash
1350 $contexts = str_replace('/', ',', $contexts);
1351 $localroleids = implode(',',$localroles);
1352 $wherelocalroles="OR (rc.roleid IN ({$localroleids})
1353 AND ctx.id IN ($contexts))" ;
1356 // We will want overrides for all of them
1358 if ($roleids = implode(',',array_merge($courseroles,$localroles))) {
1359 $whereroles = "rc.roleid IN ($roleids) AND";
1361 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1362 FROM {$CFG->prefix}role_capabilities rc
1363 JOIN {$CFG->prefix}context ctx
1364 ON rc.contextid=ctx.id
1366 (ctx.id={$context->id} OR ctx.path LIKE '{$context->path}/%'))
1368 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1370 $newrdefs = array();
1371 if ($rs = get_recordset_sql($sql)) {
1372 while ($rd = rs_fetch_next_record($rs)) {
1373 $k = "{$rd->path}:{$rd->roleid}";
1374 if (!array_key_exists($k, $newrdefs)) {
1375 $newrdefs[$k] = array();
1377 $newrdefs[$k][$rd->capability
] = $rd->permission
;
1381 debugging('Bad SQL encountered!');
1384 compact_rdefs($newrdefs);
1385 foreach ($newrdefs as $key=>$value) {
1386 $accessdata['rdef'][$key] =& $newrdefs[$key];
1389 // error_log("loaded {$context->path}");
1390 $accessdata['loaded'][] = $context->path
;
1394 * It add to the access ctrl array the data
1395 * needed by a role for a given context.
1397 * The data is added in the rdef key.
1399 * This role-centric function is useful for role_switching
1400 * and to get an overview of what a role gets under a
1401 * given context and below...
1403 * @param $roleid integer - the id of the user
1404 * @param $context context obj - needs path!
1405 * @param $accessdata accessdata array
1408 function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
1412 /* Get the relevant rolecaps into rdef
1413 * - relevant role caps
1414 * - at ctx and above
1418 if (is_null($accessdata)) {
1419 $accessdata = array(); // named list
1420 $accessdata['ra'] = array();
1421 $accessdata['rdef'] = array();
1422 $accessdata['loaded'] = array();
1425 $contexts = substr($context->path
, 1); // kill leading slash
1426 $contexts = str_replace('/', ',', $contexts);
1429 // Walk up and down the tree to grab all the roledefs
1430 // of interest to our role...
1432 // NOTE: we use an IN clauses here - which
1433 // might explode on huge sites with very convoluted nesting of
1434 // categories... - extremely unlikely that the number of nested
1435 // categories is so large that we hit the limits of IN()
1437 $sql = "SELECT ctx.path, rc.capability, rc.permission
1438 FROM {$CFG->prefix}role_capabilities rc
1439 JOIN {$CFG->prefix}context ctx
1440 ON rc.contextid=ctx.id
1441 WHERE rc.roleid=$roleid AND
1442 ( ctx.id IN ($contexts) OR
1443 ctx.path LIKE '{$context->path}/%' )
1444 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1446 $rs = get_recordset_sql($sql);
1447 while ($rd = rs_fetch_next_record($rs)) {
1448 $k = "{$rd->path}:{$roleid}";
1449 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1457 * Load accessdata for a user
1458 * into the $ACCESS global
1460 * Used by has_capability() - but feel free
1461 * to call it if you are about to run a BIG
1462 * cron run across a bazillion users.
1465 function load_user_accessdata($userid) {
1466 global $ACCESS,$CFG;
1468 $base = '/'.SYSCONTEXTID
;
1470 $accessdata = get_user_access_sitewide($userid);
1471 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
1473 // provide "default role" & set 'dr'
1475 if (!empty($CFG->defaultuserroleid
)) {
1476 $accessdata = get_role_access($CFG->defaultuserroleid
, $accessdata);
1477 if (!isset($accessdata['ra'][$base])) {
1478 $accessdata['ra'][$base] = array($CFG->defaultuserroleid
);
1480 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid
);
1482 $accessdata['dr'] = $CFG->defaultuserroleid
;
1486 // provide "default frontpage role"
1488 if (!empty($CFG->defaultfrontpageroleid
)) {
1489 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
1490 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid
, $accessdata);
1491 if (!isset($accessdata['ra'][$base])) {
1492 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid
);
1494 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid
);
1497 // for dirty timestamps in cron
1498 $accessdata['time'] = time();
1500 $ACCESS[$userid] = $accessdata;
1501 compact_rdefs($ACCESS[$userid]['rdef']);
1507 * Use shared copy of role definistions stored in $RDEFS;
1508 * @param array $rdefs array of role definitions in contexts
1510 function compact_rdefs(&$rdefs) {
1514 * This is a basic sharing only, we could also
1515 * use md5 sums of values. The main purpose is to
1516 * reduce mem in cron jobs - many users in $ACCESS array.
1519 foreach ($rdefs as $key => $value) {
1520 if (!array_key_exists($key, $RDEFS)) {
1521 $RDEFS[$key] = $rdefs[$key];
1523 $rdefs[$key] =& $RDEFS[$key];
1528 * A convenience function to completely load all the capabilities
1529 * for the current user. This is what gets called from complete_user_login()
1530 * for example. Call it only _after_ you've setup $USER and called
1531 * check_enrolment_plugins();
1534 function load_all_capabilities() {
1535 global $USER, $CFG, $DIRTYCONTEXTS;
1537 $base = '/'.SYSCONTEXTID
;
1539 if (isguestuser()) {
1540 $guest = get_guest_role();
1543 $USER->access
= get_role_access($guest->id
);
1544 // Put the ghost enrolment in place...
1545 $USER->access
['ra'][$base] = array($guest->id
);
1548 } else if (isloggedin()) {
1550 $accessdata = get_user_access_sitewide($USER->id
);
1553 // provide "default role" & set 'dr'
1555 if (!empty($CFG->defaultuserroleid
)) {
1556 $accessdata = get_role_access($CFG->defaultuserroleid
, $accessdata);
1557 if (!isset($accessdata['ra'][$base])) {
1558 $accessdata['ra'][$base] = array($CFG->defaultuserroleid
);
1560 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid
);
1562 $accessdata['dr'] = $CFG->defaultuserroleid
;
1565 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
1568 // provide "default frontpage role"
1570 if (!empty($CFG->defaultfrontpageroleid
)) {
1571 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
1572 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid
, $accessdata);
1573 if (!isset($accessdata['ra'][$base])) {
1574 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid
);
1576 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid
);
1579 $USER->access
= $accessdata;
1581 } else if (!empty($CFG->notloggedinroleid
)) {
1582 $USER->access
= get_role_access($CFG->notloggedinroleid
);
1583 $USER->access
['ra'][$base] = array($CFG->notloggedinroleid
);
1586 // Timestamp to read dirty context timestamps later
1587 $USER->access
['time'] = time();
1588 $DIRTYCONTEXTS = array();
1590 // Clear to force a refresh
1591 unset($USER->mycourses
);
1595 * A convenience function to completely reload all the capabilities
1596 * for the current user when roles have been updated in a relevant
1597 * context -- but PRESERVING switchroles and loginas.
1599 * That is - completely transparent to the user.
1601 * Note: rewrites $USER->access completely.
1604 function reload_all_capabilities() {
1607 // error_log("reloading");
1610 if (isset($USER->access
['rsw'])) {
1611 $sw = $USER->access
['rsw'];
1612 // error_log(print_r($sw,1));
1615 unset($USER->access
);
1616 unset($USER->mycourses
);
1618 load_all_capabilities();
1620 foreach ($sw as $path => $roleid) {
1621 $context = get_record('context', 'path', $path);
1622 role_switch($roleid, $context);
1628 * Adds a temp role to an accessdata array.
1630 * Useful for the "temporary guest" access
1631 * we grant to logged-in users.
1633 * Note - assumes a course context!
1636 function load_temp_role($context, $roleid, $accessdata) {
1641 // Load rdefs for the role in -
1643 // - all the parents
1644 // - and below - IOWs overrides...
1647 // turn the path into a list of context ids
1648 $contexts = substr($context->path
, 1); // kill leading slash
1649 $contexts = str_replace('/', ',', $contexts);
1651 $sql = "SELECT ctx.path,
1652 rc.capability, rc.permission
1653 FROM {$CFG->prefix}context ctx
1654 JOIN {$CFG->prefix}role_capabilities rc
1655 ON rc.contextid=ctx.id
1656 WHERE (ctx.id IN ($contexts)
1657 OR ctx.path LIKE '{$context->path}/%')
1658 AND rc.roleid = {$roleid}
1659 ORDER BY ctx.depth, ctx.path";
1660 $rs = get_recordset_sql($sql);
1661 while ($rd = rs_fetch_next_record($rs)) {
1662 $k = "{$rd->path}:{$roleid}";
1663 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1668 // Say we loaded everything for the course context
1669 // - which we just did - if the user gets a proper
1670 // RA in this session, this data will need to be reloaded,
1671 // but that is handled by the complete accessdata reload
1673 array_push($accessdata['loaded'], $context->path
);
1678 if (isset($accessdata['ra'][$context->path
])) {
1679 array_push($accessdata['ra'][$context->path
], $roleid);
1681 $accessdata['ra'][$context->path
] = array($roleid);
1689 * Check all the login enrolment information for the given user object
1690 * by querying the enrolment plugins
1692 function check_enrolment_plugins(&$user) {
1695 static $inprogress; // To prevent this function being called more than once in an invocation
1697 if (!empty($inprogress[$user->id
])) {
1701 $inprogress[$user->id
] = true; // Set the flag
1703 require_once($CFG->dirroot
.'/enrol/enrol.class.php');
1705 if (!($plugins = explode(',', $CFG->enrol_plugins_enabled
))) {
1706 $plugins = array($CFG->enrol
);
1709 foreach ($plugins as $plugin) {
1710 $enrol = enrolment_factory
::factory($plugin);
1711 if (method_exists($enrol, 'setup_enrolments')) { /// Plugin supports Roles (Moodle 1.7 and later)
1712 $enrol->setup_enrolments($user);
1713 } else { /// Run legacy enrolment methods
1714 if (method_exists($enrol, 'get_student_courses')) {
1715 $enrol->get_student_courses($user);
1717 if (method_exists($enrol, 'get_teacher_courses')) {
1718 $enrol->get_teacher_courses($user);
1721 /// deal with $user->students and $user->teachers stuff
1722 unset($user->student
);
1723 unset($user->teacher
);
1728 unset($inprogress[$user->id
]); // Unset the flag
1732 * Installs the roles system.
1733 * This function runs on a fresh install as well as on an upgrade from the old
1734 * hard-coded student/teacher/admin etc. roles to the new roles system.
1736 function moodle_install_roles() {
1740 /// Create a system wide context for assignemnt.
1741 $systemcontext = $context = get_context_instance(CONTEXT_SYSTEM
);
1744 /// Create default/legacy roles and capabilities.
1745 /// (1 legacy capability per legacy role at system level).
1747 $adminrole = create_role(addslashes(get_string('administrator')), 'admin',
1748 addslashes(get_string('administratordescription')), 'moodle/legacy:admin');
1749 $coursecreatorrole = create_role(addslashes(get_string('coursecreators')), 'coursecreator',
1750 addslashes(get_string('coursecreatorsdescription')), 'moodle/legacy:coursecreator');
1751 $editteacherrole = create_role(addslashes(get_string('defaultcourseteacher')), 'editingteacher',
1752 addslashes(get_string('defaultcourseteacherdescription')), 'moodle/legacy:editingteacher');
1753 $noneditteacherrole = create_role(addslashes(get_string('noneditingteacher')), 'teacher',
1754 addslashes(get_string('noneditingteacherdescription')), 'moodle/legacy:teacher');
1755 $studentrole = create_role(addslashes(get_string('defaultcoursestudent')), 'student',
1756 addslashes(get_string('defaultcoursestudentdescription')), 'moodle/legacy:student');
1757 $guestrole = create_role(addslashes(get_string('guest')), 'guest',
1758 addslashes(get_string('guestdescription')), 'moodle/legacy:guest');
1759 $userrole = create_role(addslashes(get_string('authenticateduser')), 'user',
1760 addslashes(get_string('authenticateduserdescription')), 'moodle/legacy:user');
1762 /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
1764 if (!assign_capability('moodle/site:doanything', CAP_ALLOW
, $adminrole, $systemcontext->id
)) {
1765 error('Could not assign moodle/site:doanything to the admin role');
1767 if (!update_capabilities()) {
1768 error('Had trouble upgrading the core capabilities for the Roles System');
1771 /// Look inside user_admin, user_creator, user_teachers, user_students and
1772 /// assign above new roles. If a user has both teacher and student role,
1773 /// only teacher role is assigned. The assignment should be system level.
1775 $dbtables = $db->MetaTables('TABLES');
1777 /// Set up the progress bar
1779 $usertables = array('user_admins', 'user_coursecreators', 'user_teachers', 'user_students');
1781 $totalcount = $progresscount = 0;
1782 foreach ($usertables as $usertable) {
1783 if (in_array($CFG->prefix
.$usertable, $dbtables)) {
1784 $totalcount +
= count_records($usertable);
1788 print_progress(0, $totalcount, 5, 1, 'Processing role assignments');
1790 /// Upgrade the admins.
1791 /// Sort using id ASC, first one is primary admin.
1793 if (in_array($CFG->prefix
.'user_admins', $dbtables)) {
1794 if ($rs = get_recordset_sql('SELECT * from '.$CFG->prefix
.'user_admins ORDER BY ID ASC')) {
1795 while ($admin = rs_fetch_next_record($rs)) {
1796 role_assign($adminrole, $admin->userid
, 0, $systemcontext->id
);
1798 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1803 // This is a fresh install.
1807 /// Upgrade course creators.
1808 if (in_array($CFG->prefix
.'user_coursecreators', $dbtables)) {
1809 if ($rs = get_recordset('user_coursecreators')) {
1810 while ($coursecreator = rs_fetch_next_record($rs)) {
1811 role_assign($coursecreatorrole, $coursecreator->userid
, 0, $systemcontext->id
);
1813 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1820 /// Upgrade editting teachers and non-editting teachers.
1821 if (in_array($CFG->prefix
.'user_teachers', $dbtables)) {
1822 if ($rs = get_recordset('user_teachers')) {
1823 while ($teacher = rs_fetch_next_record($rs)) {
1825 // removed code here to ignore site level assignments
1826 // since the contexts are separated now
1828 // populate the user_lastaccess table
1829 $access = new object();
1830 $access->timeaccess
= $teacher->timeaccess
;
1831 $access->userid
= $teacher->userid
;
1832 $access->courseid
= $teacher->course
;
1833 insert_record('user_lastaccess', $access);
1835 // assign the default student role
1836 $coursecontext = get_context_instance(CONTEXT_COURSE
, $teacher->course
); // needs cache
1838 if ($teacher->authority
== 0) {
1844 if ($teacher->editall
) { // editting teacher
1845 role_assign($editteacherrole, $teacher->userid
, 0, $coursecontext->id
, $teacher->timestart
, $teacher->timeend
, $hiddenteacher, $teacher->enrol
, $teacher->timemodified
);
1847 role_assign($noneditteacherrole, $teacher->userid
, 0, $coursecontext->id
, $teacher->timestart
, $teacher->timeend
, $hiddenteacher, $teacher->enrol
, $teacher->timemodified
);
1850 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1857 /// Upgrade students.
1858 if (in_array($CFG->prefix
.'user_students', $dbtables)) {
1859 if ($rs = get_recordset('user_students')) {
1860 while ($student = rs_fetch_next_record($rs)) {
1862 // populate the user_lastaccess table
1863 $access = new object;
1864 $access->timeaccess
= $student->timeaccess
;
1865 $access->userid
= $student->userid
;
1866 $access->courseid
= $student->course
;
1867 insert_record('user_lastaccess', $access);
1869 // assign the default student role
1870 $coursecontext = get_context_instance(CONTEXT_COURSE
, $student->course
);
1871 role_assign($studentrole, $student->userid
, 0, $coursecontext->id
, $student->timestart
, $student->timeend
, 0, $student->enrol
, $student->time
);
1873 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1880 /// Upgrade guest (only 1 entry).
1881 if ($guestuser = get_record('user', 'username', 'guest')) {
1882 role_assign($guestrole, $guestuser->id
, 0, $systemcontext->id
);
1884 print_progress($totalcount, $totalcount, 5, 1, 'Processing role assignments');
1887 /// Insert the correct records for legacy roles
1888 allow_assign($adminrole, $adminrole);
1889 allow_assign($adminrole, $coursecreatorrole);
1890 allow_assign($adminrole, $noneditteacherrole);
1891 allow_assign($adminrole, $editteacherrole);
1892 allow_assign($adminrole, $studentrole);
1893 allow_assign($adminrole, $guestrole);
1895 allow_assign($coursecreatorrole, $noneditteacherrole);
1896 allow_assign($coursecreatorrole, $editteacherrole);
1897 allow_assign($coursecreatorrole, $studentrole);
1898 allow_assign($coursecreatorrole, $guestrole);
1900 allow_assign($editteacherrole, $noneditteacherrole);
1901 allow_assign($editteacherrole, $studentrole);
1902 allow_assign($editteacherrole, $guestrole);
1904 /// Set up default allow override matrix
1905 allow_override($adminrole, $adminrole);
1906 allow_override($adminrole, $coursecreatorrole);
1907 allow_override($adminrole, $noneditteacherrole);
1908 allow_override($adminrole, $editteacherrole);
1909 allow_override($adminrole, $studentrole);
1910 allow_override($adminrole, $guestrole);
1911 allow_override($adminrole, $userrole);
1914 //allow_override($editteacherrole, $noneditteacherrole);
1915 //allow_override($editteacherrole, $studentrole);
1916 //allow_override($editteacherrole, $guestrole);
1919 /// Delete the old user tables when we are done
1921 $tables = array('user_students', 'user_teachers', 'user_coursecreators', 'user_admins');
1922 foreach ($tables as $tablename) {
1923 $table = new XMLDBTable($tablename);
1924 if (table_exists($table)) {
1931 * Returns array of all legacy roles.
1933 function get_legacy_roles() {
1935 'admin' => 'moodle/legacy:admin',
1936 'coursecreator' => 'moodle/legacy:coursecreator',
1937 'editingteacher' => 'moodle/legacy:editingteacher',
1938 'teacher' => 'moodle/legacy:teacher',
1939 'student' => 'moodle/legacy:student',
1940 'guest' => 'moodle/legacy:guest',
1941 'user' => 'moodle/legacy:user'
1945 function get_legacy_type($roleid) {
1946 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
1947 $legacyroles = get_legacy_roles();
1950 foreach($legacyroles as $ltype=>$lcap) {
1951 $localoverride = get_local_override($roleid, $sitecontext->id
, $lcap);
1952 if (!empty($localoverride->permission
) and $localoverride->permission
== CAP_ALLOW
) {
1953 //choose first selected legacy capability - reset the rest
1954 if (empty($result)) {
1957 unassign_capability($lcap, $roleid);
1966 * Assign the defaults found in this capabality definition to roles that have
1967 * the corresponding legacy capabilities assigned to them.
1968 * @param $legacyperms - an array in the format (example):
1969 * 'guest' => CAP_PREVENT,
1970 * 'student' => CAP_ALLOW,
1971 * 'teacher' => CAP_ALLOW,
1972 * 'editingteacher' => CAP_ALLOW,
1973 * 'coursecreator' => CAP_ALLOW,
1974 * 'admin' => CAP_ALLOW
1975 * @return boolean - success or failure.
1977 function assign_legacy_capabilities($capability, $legacyperms) {
1979 $legacyroles = get_legacy_roles();
1981 foreach ($legacyperms as $type => $perm) {
1983 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
1985 if (!array_key_exists($type, $legacyroles)) {
1986 error('Incorrect legacy role definition for type: '.$type);
1989 if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW
)) {
1990 foreach ($roles as $role) {
1991 // Assign a site level capability.
1992 if (!assign_capability($capability, $perm, $role->id
, $systemcontext->id
)) {
2003 * Checks to see if a capability is a legacy capability.
2004 * @param $capabilityname
2007 function islegacy($capabilityname) {
2008 if (strpos($capabilityname, 'moodle/legacy') === 0) {
2017 /**********************************
2018 * Context Manipulation functions *
2019 **********************************/
2022 * Create a new context record for use by all roles-related stuff
2023 * assumes that the caller has done the homework.
2026 * @param $instanceid
2028 * @return object newly created context
2030 function create_context($contextlevel, $instanceid) {
2034 if ($contextlevel == CONTEXT_SYSTEM
) {
2035 return create_system_context();
2038 $context = new object();
2039 $context->contextlevel
= $contextlevel;
2040 $context->instanceid
= $instanceid;
2042 // Define $context->path based on the parent
2043 // context. In other words... Who is your daddy?
2044 $basepath = '/' . SYSCONTEXTID
;
2049 switch ($contextlevel) {
2050 case CONTEXT_COURSECAT
:
2051 $sql = "SELECT ctx.path, ctx.depth
2052 FROM {$CFG->prefix}context ctx
2053 JOIN {$CFG->prefix}course_categories cc
2054 ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT
.")
2055 WHERE cc.id={$instanceid}";
2056 if ($p = get_record_sql($sql)) {
2057 $basepath = $p->path
;
2058 $basedepth = $p->depth
;
2059 } else if ($category = get_record('course_categories', 'id', $instanceid)) {
2060 if (empty($category->parent
)) {
2061 // ok - this is a top category
2062 } else if ($parent = get_context_instance(CONTEXT_COURSECAT
, $category->parent
)) {
2063 $basepath = $parent->path
;
2064 $basedepth = $parent->depth
;
2066 // wrong parent category - no big deal, this can be fixed later
2071 // incorrect category id
2076 case CONTEXT_COURSE
:
2077 $sql = "SELECT ctx.path, ctx.depth
2078 FROM {$CFG->prefix}context ctx
2079 JOIN {$CFG->prefix}course c
2080 ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT
.")
2081 WHERE c.id={$instanceid} AND c.id !=" . SITEID
;
2082 if ($p = get_record_sql($sql)) {
2083 $basepath = $p->path
;
2084 $basedepth = $p->depth
;
2085 } else if ($course = get_record('course', 'id', $instanceid)) {
2086 if ($course->id
== SITEID
) {
2087 //ok - no parent category
2088 } else if ($parent = get_context_instance(CONTEXT_COURSECAT
, $course->category
)) {
2089 $basepath = $parent->path
;
2090 $basedepth = $parent->depth
;
2092 // wrong parent category of course - no big deal, this can be fixed later
2096 } else if ($instanceid == SITEID
) {
2097 // no errors for missing site course during installation
2100 // incorrect course id
2105 case CONTEXT_MODULE
:
2106 $sql = "SELECT ctx.path, ctx.depth
2107 FROM {$CFG->prefix}context ctx
2108 JOIN {$CFG->prefix}course_modules cm
2109 ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
2110 WHERE cm.id={$instanceid}";
2111 if ($p = get_record_sql($sql)) {
2112 $basepath = $p->path
;
2113 $basedepth = $p->depth
;
2114 } else if ($cm = get_record('course_modules', 'id', $instanceid)) {
2115 if ($parent = get_context_instance(CONTEXT_COURSE
, $cm->course
)) {
2116 $basepath = $parent->path
;
2117 $basedepth = $parent->depth
;
2119 // course does not exist - modules can not exist without a course
2123 // cm does not exist
2129 // Only non-pinned & course-page based
2130 $sql = "SELECT ctx.path, ctx.depth
2131 FROM {$CFG->prefix}context ctx
2132 JOIN {$CFG->prefix}block_instance bi
2133 ON (bi.pageid=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
2134 WHERE bi.id={$instanceid} AND bi.pagetype='course-view'";
2135 if ($p = get_record_sql($sql)) {
2136 $basepath = $p->path
;
2137 $basedepth = $p->depth
;
2138 } else if ($bi = get_record('block_instance', 'id', $instanceid)) {
2139 if ($bi->pagetype
!= 'course-view') {
2140 // ok - not a course block
2141 } else if ($parent = get_context_instance(CONTEXT_COURSE
, $bi->pageid
)) {
2142 $basepath = $parent->path
;
2143 $basedepth = $parent->depth
;
2145 // parent course does not exist - course blocks can not exist without a course
2149 // block does not exist
2154 // default to basepath
2158 // if grandparents unknown, maybe rebuild_context_path() will solve it later
2159 if ($basedepth != 0) {
2160 $context->depth
= $basedepth+
1;
2163 if ($result and $id = insert_record('context', $context)) {
2164 // can't set the full path till we know the id!
2165 if ($basedepth != 0 and !empty($basepath)) {
2166 set_field('context', 'path', $basepath.'/'. $id, 'id', $id);
2168 return get_context_instance_by_id($id);
2171 debugging('Error: could not insert new context level "'.
2172 s($contextlevel).'", instance "'.
2173 s($instanceid).'".');
2179 * This hacky function is needed because we can not change system context instanceid using normal upgrade routine.
2181 function get_system_context($cache=true) {
2182 static $cached = null;
2183 if ($cache and defined('SYSCONTEXTID')) {
2184 if (is_null($cached)) {
2185 $cached = new object();
2186 $cached->id
= SYSCONTEXTID
;
2187 $cached->contextlevel
= CONTEXT_SYSTEM
;
2188 $cached->instanceid
= 0;
2189 $cached->path
= '/'.SYSCONTEXTID
;
2195 if (!$context = get_record('context', 'contextlevel', CONTEXT_SYSTEM
)) {
2196 $context = new object();
2197 $context->contextlevel
= CONTEXT_SYSTEM
;
2198 $context->instanceid
= 0;
2199 $context->depth
= 1;
2200 $context->path
= NULL; //not known before insert
2202 if (!$context->id
= insert_record('context', $context)) {
2203 // better something than nothing - let's hope it will work somehow
2204 if (!defined('SYSCONTEXTID')) {
2205 define('SYSCONTEXTID', 1);
2207 debugging('Can not create system context');
2208 $context->id
= SYSCONTEXTID
;
2209 $context->path
= '/'.SYSCONTEXTID
;
2214 if (!isset($context->depth
) or $context->depth
!= 1 or $context->instanceid
!= 0 or $context->path
!= '/'.$context->id
) {
2215 $context->instanceid
= 0;
2216 $context->path
= '/'.$context->id
;
2217 $context->depth
= 1;
2218 update_record('context', $context);
2221 if (!defined('SYSCONTEXTID')) {
2222 define('SYSCONTEXTID', $context->id
);
2230 * Remove a context record and any dependent entries,
2231 * removes context from static context cache too
2233 * @param $instanceid
2235 * @return bool properly deleted
2237 function delete_context($contextlevel, $instanceid) {
2238 global $context_cache, $context_cache_id;
2240 // do not use get_context_instance(), because the related object might not exist,
2241 // or the context does not exist yet and it would be created now
2242 if ($context = get_record('context', 'contextlevel', $contextlevel, 'instanceid', $instanceid)) {
2243 $result = delete_records('role_assignments', 'contextid', $context->id
) &&
2244 delete_records('role_capabilities', 'contextid', $context->id
) &&
2245 delete_records('context', 'id', $context->id
);
2247 // do not mark dirty contexts if parents unknown
2248 if (!is_null($context->path
) and $context->depth
> 0) {
2249 mark_context_dirty($context->path
);
2252 // purge static context cache if entry present
2253 unset($context_cache[$contextlevel][$instanceid]);
2254 unset($context_cache_id[$context->id
]);
2264 * Precreates all contexts including all parents
2265 * @param int $contextlevel, empty means all
2266 * @param bool $buildpaths update paths and depths
2267 * @param bool $feedback show sql feedback
2270 function create_contexts($contextlevel=null, $buildpaths=true, $feedback=false) {
2273 //make sure system context exists
2274 $syscontext = get_system_context(false);
2276 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2277 or $contextlevel == CONTEXT_COURSE
2278 or $contextlevel == CONTEXT_MODULE
2279 or $contextlevel == CONTEXT_BLOCK
) {
2280 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2281 SELECT ".CONTEXT_COURSECAT
.", cc.id
2282 FROM {$CFG->prefix}course_categories cc
2283 WHERE NOT EXISTS (SELECT 'x'
2284 FROM {$CFG->prefix}context cx
2285 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT
.")";
2286 execute_sql($sql, $feedback);
2290 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2291 or $contextlevel == CONTEXT_MODULE
2292 or $contextlevel == CONTEXT_BLOCK
) {
2293 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2294 SELECT ".CONTEXT_COURSE
.", c.id
2295 FROM {$CFG->prefix}course c
2296 WHERE NOT EXISTS (SELECT 'x'
2297 FROM {$CFG->prefix}context cx
2298 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE
.")";
2299 execute_sql($sql, $feedback);
2303 if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
) {
2304 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2305 SELECT ".CONTEXT_MODULE
.", cm.id
2306 FROM {$CFG->prefix}course_modules cm
2307 WHERE NOT EXISTS (SELECT 'x'
2308 FROM {$CFG->prefix}context cx
2309 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE
.")";
2310 execute_sql($sql, $feedback);
2313 if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK
) {
2314 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2315 SELECT ".CONTEXT_BLOCK
.", bi.id
2316 FROM {$CFG->prefix}block_instance bi
2317 WHERE NOT EXISTS (SELECT 'x'
2318 FROM {$CFG->prefix}context cx
2319 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK
.")";
2320 execute_sql($sql, $feedback);
2323 if (empty($contextlevel) or $contextlevel == CONTEXT_USER
) {
2324 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2325 SELECT ".CONTEXT_USER
.", u.id
2326 FROM {$CFG->prefix}user u
2328 AND NOT EXISTS (SELECT 'x'
2329 FROM {$CFG->prefix}context cx
2330 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER
.")";
2331 execute_sql($sql, $feedback);
2336 build_context_path(false, $feedback);
2341 * Remove stale context records
2345 function cleanup_contexts() {
2348 $sql = " SELECT c.contextlevel,
2349 c.instanceid AS instanceid
2350 FROM {$CFG->prefix}context c
2351 LEFT OUTER JOIN {$CFG->prefix}course_categories t
2352 ON c.instanceid = t.id
2353 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_COURSECAT
. "
2355 SELECT c.contextlevel,
2357 FROM {$CFG->prefix}context c
2358 LEFT OUTER JOIN {$CFG->prefix}course t
2359 ON c.instanceid = t.id
2360 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_COURSE
. "
2362 SELECT c.contextlevel,
2364 FROM {$CFG->prefix}context c
2365 LEFT OUTER JOIN {$CFG->prefix}course_modules t
2366 ON c.instanceid = t.id
2367 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_MODULE
. "
2369 SELECT c.contextlevel,
2371 FROM {$CFG->prefix}context c
2372 LEFT OUTER JOIN {$CFG->prefix}user t
2373 ON c.instanceid = t.id
2374 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_USER
. "
2376 SELECT c.contextlevel,
2378 FROM {$CFG->prefix}context c
2379 LEFT OUTER JOIN {$CFG->prefix}block_instance t
2380 ON c.instanceid = t.id
2381 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_BLOCK
. "
2383 SELECT c.contextlevel,
2385 FROM {$CFG->prefix}context c
2386 LEFT OUTER JOIN {$CFG->prefix}groups t
2387 ON c.instanceid = t.id
2388 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_GROUP
. "
2390 if ($rs = get_recordset_sql($sql)) {
2393 while ($tx && $ctx = rs_fetch_next_record($rs)) {
2394 $tx = $tx && delete_context($ctx->contextlevel
, $ctx->instanceid
);
2409 * Get the context instance as an object. This function will create the
2410 * context instance if it does not exist yet.
2411 * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2412 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2413 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on.
2414 * @return object The context object.
2416 function get_context_instance($contextlevel, $instance=0) {
2418 global $context_cache, $context_cache_id, $CFG;
2419 static $allowed_contexts = array(CONTEXT_SYSTEM
, CONTEXT_USER
, CONTEXT_COURSECAT
, CONTEXT_COURSE
, CONTEXT_GROUP
, CONTEXT_MODULE
, CONTEXT_BLOCK
);
2421 if ($contextlevel === 'clearcache') {
2422 // TODO: Remove for v2.0
2423 // No longer needed, but we'll catch it to avoid erroring out on custom code.
2424 // This used to be a fix for MDL-9016
2425 // "Restoring into existing course, deleting first
2426 // deletes context and doesn't recreate it"
2430 /// System context has special cache
2431 if ($contextlevel == CONTEXT_SYSTEM
) {
2432 return get_system_context();
2435 /// check allowed context levels
2436 if (!in_array($contextlevel, $allowed_contexts)) {
2437 // fatal error, code must be fixed - probably typo or switched parameters
2438 error('Error: get_context_instance() called with incorrect context level "'.s($contextlevel).'"');
2441 if (!is_array($instance)) {
2443 if (isset($context_cache[$contextlevel][$instance])) { // Already cached
2444 return $context_cache[$contextlevel][$instance];
2447 /// Get it from the database, or create it
2448 if (!$context = get_record('context', 'contextlevel', $contextlevel, 'instanceid', $instance)) {
2449 $context = create_context($contextlevel, $instance);
2452 /// Only add to cache if context isn't empty.
2453 if (!empty($context)) {
2454 $context_cache[$contextlevel][$instance] = $context; // Cache it for later
2455 $context_cache_id[$context->id
] = $context; // Cache it for later
2462 /// ok, somebody wants to load several contexts to save some db queries ;-)
2463 $instances = $instance;
2466 foreach ($instances as $key=>$instance) {
2467 /// Check the cache first
2468 if (isset($context_cache[$contextlevel][$instance])) { // Already cached
2469 $result[$instance] = $context_cache[$contextlevel][$instance];
2470 unset($instances[$key]);
2476 if (count($instances) > 1) {
2477 $instanceids = implode(',', $instances);
2478 $instanceids = "instanceid IN ($instanceids)";
2480 $instance = reset($instances);
2481 $instanceids = "instanceid = $instance";
2484 if (!$contexts = get_records_sql("SELECT instanceid, id, contextlevel, path, depth
2485 FROM {$CFG->prefix}context
2486 WHERE contextlevel=$contextlevel AND $instanceids")) {
2487 $contexts = array();
2490 foreach ($instances as $instance) {
2491 if (isset($contexts[$instance])) {
2492 $context = $contexts[$instance];
2494 $context = create_context($contextlevel, $instance);
2497 if (!empty($context)) {
2498 $context_cache[$contextlevel][$instance] = $context; // Cache it for later
2499 $context_cache_id[$context->id
] = $context; // Cache it for later
2502 $result[$instance] = $context;
2511 * Get a context instance as an object, from a given context id.
2512 * @param mixed $id a context id or array of ids.
2513 * @return mixed object or array of the context object.
2515 function get_context_instance_by_id($id) {
2517 global $context_cache, $context_cache_id;
2519 if ($id == SYSCONTEXTID
) {
2520 return get_system_context();
2523 if (isset($context_cache_id[$id])) { // Already cached
2524 return $context_cache_id[$id];
2527 if ($context = get_record('context', 'id', $id)) { // Update the cache and return
2528 $context_cache[$context->contextlevel
][$context->instanceid
] = $context;
2529 $context_cache_id[$context->id
] = $context;
2538 * Get the local override (if any) for a given capability in a role in a context
2541 * @param $capability
2543 function get_local_override($roleid, $contextid, $capability) {
2544 return get_record('role_capabilities', 'roleid', $roleid, 'capability', $capability, 'contextid', $contextid);
2549 /************************************
2550 * DB TABLE RELATED FUNCTIONS *
2551 ************************************/
2554 * function that creates a role
2555 * @param name - role name
2556 * @param shortname - role short name
2557 * @param description - role description
2558 * @param legacy - optional legacy capability
2559 * @return id or false
2561 function create_role($name, $shortname, $description, $legacy='') {
2563 // check for duplicate role name
2565 if ($role = get_record('role','name', $name)) {
2566 error('there is already a role with this name!');
2569 if ($role = get_record('role','shortname', $shortname)) {
2570 error('there is already a role with this shortname!');
2573 $role = new object();
2574 $role->name
= $name;
2575 $role->shortname
= $shortname;
2576 $role->description
= $description;
2578 //find free sortorder number
2579 $role->sortorder
= count_records('role');
2580 while (get_record('role','sortorder', $role->sortorder
)) {
2581 $role->sortorder +
= 1;
2584 if (!$context = get_context_instance(CONTEXT_SYSTEM
)) {
2588 if ($id = insert_record('role', $role)) {
2590 assign_capability($legacy, CAP_ALLOW
, $id, $context->id
);
2593 /// By default, users with role:manage at site level
2594 /// should be able to assign users to this new role, and override this new role's capabilities
2596 // find all admin roles
2597 if ($adminroles = get_roles_with_capability('moodle/role:manage', CAP_ALLOW
, $context)) {
2598 // foreach admin role
2599 foreach ($adminroles as $arole) {
2600 // write allow_assign and allow_overrid
2601 allow_assign($arole->id
, $id);
2602 allow_override($arole->id
, $id);
2614 * function that deletes a role and cleanups up after it
2615 * @param roleid - id of role to delete
2618 function delete_role($roleid) {
2622 // mdl 10149, check if this is the last active admin role
2623 // if we make the admin role not deletable then this part can go
2625 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
2627 if ($role = get_record('role', 'id', $roleid)) {
2628 if (record_exists('role_capabilities', 'contextid', $systemcontext->id
, 'roleid', $roleid, 'capability', 'moodle/site:doanything')) {
2629 // deleting an admin role
2631 if ($adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW
, $systemcontext)) {
2632 foreach ($adminroles as $adminrole) {
2633 if ($adminrole->id
!= $roleid) {
2634 // some other admin role
2635 if (record_exists('role_assignments', 'roleid', $adminrole->id
, 'contextid', $systemcontext->id
)) {
2636 // found another admin role with at least 1 user assigned
2643 if ($status !== true) {
2644 error ('You can not delete this role because there is no other admin roles with users assigned');
2649 // first unssign all users
2650 if (!role_unassign($roleid)) {
2651 debugging("Error while unassigning all users from role with ID $roleid!");
2655 // cleanup all references to this role, ignore errors
2658 // MDL-10679 find all contexts where this role has an override
2659 $contexts = get_records_sql("SELECT contextid, contextid
2660 FROM {$CFG->prefix}role_capabilities
2661 WHERE roleid = $roleid");
2663 delete_records('role_capabilities', 'roleid', $roleid);
2665 delete_records('role_allow_assign', 'roleid', $roleid);
2666 delete_records('role_allow_assign', 'allowassign', $roleid);
2667 delete_records('role_allow_override', 'roleid', $roleid);
2668 delete_records('role_allow_override', 'allowoverride', $roleid);
2669 delete_records('role_names', 'roleid', $roleid);
2672 // finally delete the role itself
2673 // get this before the name is gone for logging
2674 $rolename = get_field('role', 'name', 'id', $roleid);
2676 if ($success and !delete_records('role', 'id', $roleid)) {
2677 debugging("Could not delete role record with ID $roleid!");
2682 add_to_log(SITEID
, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '', $USER->id
);
2689 * Function to write context specific overrides, or default capabilities.
2690 * @param module - string name
2691 * @param capability - string name
2692 * @param contextid - context id
2693 * @param roleid - role id
2694 * @param permission - int 1,-1 or -1000
2695 * should not be writing if permission is 0
2697 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
2701 if (empty($permission) ||
$permission == CAP_INHERIT
) { // if permission is not set
2702 unassign_capability($capability, $roleid, $contextid);
2706 $existing = get_record('role_capabilities', 'contextid', $contextid, 'roleid', $roleid, 'capability', $capability);
2708 if ($existing and !$overwrite) { // We want to keep whatever is there already
2713 $cap->contextid
= $contextid;
2714 $cap->roleid
= $roleid;
2715 $cap->capability
= $capability;
2716 $cap->permission
= $permission;
2717 $cap->timemodified
= time();
2718 $cap->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2721 $cap->id
= $existing->id
;
2722 return update_record('role_capabilities', $cap);
2724 $c = get_record('context', 'id', $contextid);
2725 return insert_record('role_capabilities', $cap);
2730 * Unassign a capability from a role.
2731 * @param $roleid - the role id
2732 * @param $capability - the name of the capability
2733 * @return boolean - success or failure
2735 function unassign_capability($capability, $roleid, $contextid=NULL) {
2737 if (isset($contextid)) {
2738 // delete from context rel, if this is the last override in this context
2739 $status = delete_records('role_capabilities', 'capability', $capability,
2740 'roleid', $roleid, 'contextid', $contextid);
2742 $status = delete_records('role_capabilities', 'capability', $capability,
2750 * Get the roles that have a given capability assigned to it. This function
2751 * does not resolve the actual permission of the capability. It just checks
2752 * for assignment only.
2753 * @param $capability - capability name (string)
2754 * @param $permission - optional, the permission defined for this capability
2755 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT
2756 * @return array or role objects
2758 function get_roles_with_capability($capability, $permission=NULL, $context='') {
2763 if ($contexts = get_parent_contexts($context)) {
2764 $listofcontexts = '('.implode(',', $contexts).')';
2766 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
2767 $listofcontexts = '('.$sitecontext->id
.')'; // must be site
2769 $contextstr = "AND (rc.contextid = '$context->id' OR rc.contextid IN $listofcontexts)";
2774 $selectroles = "SELECT r.*
2775 FROM {$CFG->prefix}role r,
2776 {$CFG->prefix}role_capabilities rc
2777 WHERE rc.capability = '$capability'
2778 AND rc.roleid = r.id $contextstr";
2780 if (isset($permission)) {
2781 $selectroles .= " AND rc.permission = '$permission'";
2783 return get_records_sql($selectroles);
2788 * This function makes a role-assignment (a role for a user or group in a particular context)
2789 * @param $roleid - the role of the id
2790 * @param $userid - userid
2791 * @param $groupid - group id
2792 * @param $contextid - id of the context
2793 * @param $timestart - time this assignment becomes effective
2794 * @param $timeend - time this assignemnt ceases to be effective
2796 * @return id - new id of the assigment
2798 function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden=0, $enrol='manual',$timemodified='') {
2801 /// Do some data validation
2803 if (empty($roleid)) {
2804 debugging('Role ID not provided');
2808 if (empty($userid) && empty($groupid)) {
2809 debugging('Either userid or groupid must be provided');
2813 if ($userid && !record_exists('user', 'id', $userid)) {
2814 debugging('User ID '.intval($userid).' does not exist!');
2818 if ($groupid && !groups_group_exists($groupid)) {
2819 debugging('Group ID '.intval($groupid).' does not exist!');
2823 if (!$context = get_context_instance_by_id($contextid)) {
2824 debugging('Context ID '.intval($contextid).' does not exist!');
2828 if (($timestart and $timeend) and ($timestart > $timeend)) {
2829 debugging('The end time can not be earlier than the start time');
2833 if (!$timemodified) {
2834 $timemodified = time();
2837 /// Check for existing entry
2839 $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id
, 'userid', $userid);
2841 $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id
, 'groupid', $groupid);
2844 if (empty($ra)) { // Create a new entry
2846 $ra->roleid
= $roleid;
2847 $ra->contextid
= $context->id
;
2848 $ra->userid
= $userid;
2849 $ra->hidden
= $hidden;
2850 $ra->enrol
= $enrol;
2851 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2852 /// by repeating queries with the same exact parameters in a 100 secs time window
2853 $ra->timestart
= round($timestart, -2);
2854 $ra->timeend
= $timeend;
2855 $ra->timemodified
= $timemodified;
2856 $ra->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2858 if (!$ra->id
= insert_record('role_assignments', $ra)) {
2862 } else { // We already have one, just update it
2864 $ra->hidden
= $hidden;
2865 $ra->enrol
= $enrol;
2866 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2867 /// by repeating queries with the same exact parameters in a 100 secs time window
2868 $ra->timestart
= round($timestart, -2);
2869 $ra->timeend
= $timeend;
2870 $ra->timemodified
= $timemodified;
2871 $ra->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2873 if (!update_record('role_assignments', $ra)) {
2878 /// mark context as dirty - modules might use has_capability() in xxx_role_assing()
2879 /// again expensive, but needed
2880 mark_context_dirty($context->path
);
2882 if (!empty($USER->id
) && $USER->id
== $userid) {
2883 /// If the user is the current user, then do full reload of capabilities too.
2884 load_all_capabilities();
2887 /// Ask all the modules if anything needs to be done for this user
2888 if ($mods = get_list_of_plugins('mod')) {
2889 foreach ($mods as $mod) {
2890 include_once($CFG->dirroot
.'/mod/'.$mod.'/lib.php');
2891 $functionname = $mod.'_role_assign';
2892 if (function_exists($functionname)) {
2893 $functionname($userid, $context, $roleid);
2898 /// now handle metacourse role assignments if in course context
2899 if ($context->contextlevel
== CONTEXT_COURSE
) {
2900 if ($parents = get_records('course_meta', 'child_course', $context->instanceid
)) {
2901 foreach ($parents as $parent) {
2902 sync_metacourse($parent->parent_course
);
2907 events_trigger('role_assigned', $ra);
2914 * Deletes one or more role assignments. You must specify at least one parameter.
2919 * @param $enrol unassign only if enrolment type matches, NULL means anything
2920 * @return boolean - success or failure
2922 function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NULL) {
2924 require_once($CFG->dirroot
.'/group/lib.php');
2928 $args = array('roleid', 'userid', 'groupid', 'contextid');
2930 foreach ($args as $arg) {
2932 $select[] = $arg.' = '.$
$arg;
2935 if (!empty($enrol)) {
2936 $select[] = "enrol='$enrol'";
2940 if ($ras = get_records_select('role_assignments', implode(' AND ', $select))) {
2941 $mods = get_list_of_plugins('mod');
2942 foreach($ras as $ra) {
2944 /// infinite loop protection when deleting recursively
2945 if (!$ra = get_record('role_assignments', 'id', $ra->id
)) {
2948 if (delete_records('role_assignments', 'id', $ra->id
)) {
2954 if (!$context = get_context_instance_by_id($ra->contextid
)) {
2955 // strange error, not much to do
2959 /* mark contexts as dirty here, because we need the refreshed
2960 * caps bellow to delete group membership and user_lastaccess!
2961 * and yes, this is very expensive for bulk operations :-(
2963 mark_context_dirty($context->path
);
2965 /// If the user is the current user, then do full reload of capabilities too.
2966 if (!empty($USER->id
) && $USER->id
== $ra->userid
) {
2967 load_all_capabilities();
2970 /// Ask all the modules if anything needs to be done for this user
2971 foreach ($mods as $mod) {
2972 include_once($CFG->dirroot
.'/mod/'.$mod.'/lib.php');
2973 $functionname = $mod.'_role_unassign';
2974 if (function_exists($functionname)) {
2975 $functionname($ra->userid
, $context); // watch out, $context might be NULL if something goes wrong
2979 /// now handle metacourse role unassigment and removing from goups if in course context
2980 if ($context->contextlevel
== CONTEXT_COURSE
) {
2982 // cleanup leftover course groups/subscriptions etc when user has
2983 // no capability to view course
2984 // this may be slow, but this is the proper way of doing it
2985 if (!has_capability('moodle/course:view', $context, $ra->userid
)) {
2986 // remove from groups
2987 groups_delete_group_members($context->instanceid
, $ra->userid
);
2989 // delete lastaccess records
2990 delete_records('user_lastaccess', 'userid', $ra->userid
, 'courseid', $context->instanceid
);
2993 //unassign roles in metacourses if needed
2994 if ($parents = get_records('course_meta', 'child_course', $context->instanceid
)) {
2995 foreach ($parents as $parent) {
2996 sync_metacourse($parent->parent_course
);
3002 events_trigger('role_unassigned', $ra);
3012 * A convenience function to take care of the common case where you
3013 * just want to enrol someone using the default role into a course
3015 * @param object $course
3016 * @param object $user
3017 * @param string $enrol - the plugin used to do this enrolment
3019 function enrol_into_course($course, $user, $enrol) {
3021 $timestart = time();
3022 // remove time part from the timestamp and keep only the date part
3023 $timestart = make_timestamp(date('Y', $timestart), date('m', $timestart), date('d', $timestart), 0, 0, 0);
3024 if ($course->enrolperiod
) {
3025 $timeend = $timestart +
$course->enrolperiod
;
3030 if ($role = get_default_course_role($course)) {
3032 $context = get_context_instance(CONTEXT_COURSE
, $course->id
);
3034 if (!role_assign($role->id
, $user->id
, 0, $context->id
, $timestart, $timeend, 0, $enrol)) {
3038 // force accessdata refresh for users visiting this context...
3039 mark_context_dirty($context->path
);
3041 email_welcome_message_to_user($course, $user);
3043 add_to_log($course->id
, 'course', 'enrol',
3044 'view.php?id='.$course->id
, $course->id
);
3053 * Loads the capability definitions for the component (from file). If no
3054 * capabilities are defined for the component, we simply return an empty array.
3055 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3056 * @return array of capabilities
3058 function load_capability_def($component) {
3061 if ($component == 'moodle') {
3062 $defpath = $CFG->libdir
.'/db/access.php';
3063 $varprefix = 'moodle';
3065 $compparts = explode('/', $component);
3067 if ($compparts[0] == 'block') {
3068 // Blocks are an exception. Blocks directory is 'blocks', and not
3069 // 'block'. So we need to jump through hoops.
3070 $defpath = $CFG->dirroot
.'/'.$compparts[0].
3071 's/'.$compparts[1].'/db/access.php';
3072 $varprefix = $compparts[0].'_'.$compparts[1];
3074 } else if ($compparts[0] == 'format') {
3075 // Similar to the above, course formats are 'format' while they
3076 // are stored in 'course/format'.
3077 $defpath = $CFG->dirroot
.'/course/'.$component.'/db/access.php';
3078 $varprefix = $compparts[0].'_'.$compparts[1];
3080 } else if ($compparts[0] == 'gradeimport') {
3081 $defpath = $CFG->dirroot
.'/grade/import/'.$compparts[1].'/db/access.php';
3082 $varprefix = $compparts[0].'_'.$compparts[1];
3084 } else if ($compparts[0] == 'gradeexport') {
3085 $defpath = $CFG->dirroot
.'/grade/export/'.$compparts[1].'/db/access.php';
3086 $varprefix = $compparts[0].'_'.$compparts[1];
3088 } else if ($compparts[0] == 'gradereport') {
3089 $defpath = $CFG->dirroot
.'/grade/report/'.$compparts[1].'/db/access.php';
3090 $varprefix = $compparts[0].'_'.$compparts[1];
3093 $defpath = $CFG->dirroot
.'/'.$component.'/db/access.php';
3094 $varprefix = str_replace('/', '_', $component);
3097 $capabilities = array();
3099 if (file_exists($defpath)) {
3101 $capabilities = $
{$varprefix.'_capabilities'};
3103 return $capabilities;
3108 * Gets the capabilities that have been cached in the database for this
3110 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3111 * @return array of capabilities
3113 function get_cached_capabilities($component='moodle') {
3114 if ($component == 'moodle') {
3115 $storedcaps = get_records_select('capabilities',
3116 "name LIKE 'moodle/%:%'");
3117 } else if ($component == 'local') {
3118 $storedcaps = get_records_select('capabilities',
3119 "name LIKE 'moodle/local:%'");
3121 $storedcaps = get_records_select('capabilities',
3122 "name LIKE '$component:%'");
3128 * Returns default capabilities for given legacy role type.
3130 * @param string legacy role name
3133 function get_default_capabilities($legacyrole) {
3134 if (!$allcaps = get_records('capabilities')) {
3135 error('Error: no capabilitites defined!');
3138 $defaults = array();
3139 $components = array();
3140 foreach ($allcaps as $cap) {
3141 if (!in_array($cap->component
, $components)) {
3142 $components[] = $cap->component
;
3143 $alldefs = array_merge($alldefs, load_capability_def($cap->component
));
3146 foreach($alldefs as $name=>$def) {
3147 if (isset($def['legacy'][$legacyrole])) {
3148 $defaults[$name] = $def['legacy'][$legacyrole];
3153 $defaults['moodle/legacy:'.$legacyrole] = CAP_ALLOW
;
3154 if ($legacyrole == 'admin') {
3155 $defaults['moodle/site:doanything'] = CAP_ALLOW
;
3161 * Reset role capabilitites to default according to selected legacy capability.
3162 * If several legacy caps selected, use the first from get_default_capabilities.
3163 * If no legacy selected, removes all capabilities.
3165 * @param int @roleid
3167 function reset_role_capabilities($roleid) {
3168 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
3169 $legacyroles = get_legacy_roles();
3171 $defaultcaps = array();
3172 foreach($legacyroles as $ltype=>$lcap) {
3173 $localoverride = get_local_override($roleid, $sitecontext->id
, $lcap);
3174 if (!empty($localoverride->permission
) and $localoverride->permission
== CAP_ALLOW
) {
3175 //choose first selected legacy capability
3176 $defaultcaps = get_default_capabilities($ltype);
3181 delete_records('role_capabilities', 'roleid', $roleid);
3182 if (!empty($defaultcaps)) {
3183 foreach($defaultcaps as $cap=>$permission) {
3184 assign_capability($cap, $permission, $roleid, $sitecontext->id
);
3190 * Updates the capabilities table with the component capability definitions.
3191 * If no parameters are given, the function updates the core moodle
3194 * Note that the absence of the db/access.php capabilities definition file
3195 * will cause any stored capabilities for the component to be removed from
3198 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3201 function update_capabilities($component='moodle') {
3203 $storedcaps = array();
3205 $filecaps = load_capability_def($component);
3206 $cachedcaps = get_cached_capabilities($component);
3208 foreach ($cachedcaps as $cachedcap) {
3209 array_push($storedcaps, $cachedcap->name
);
3210 // update risk bitmasks and context levels in existing capabilities if needed
3211 if (array_key_exists($cachedcap->name
, $filecaps)) {
3212 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name
])) {
3213 $filecaps[$cachedcap->name
]['riskbitmask'] = 0; // no risk if not specified
3215 if ($cachedcap->riskbitmask
!= $filecaps[$cachedcap->name
]['riskbitmask']) {
3216 $updatecap = new object();
3217 $updatecap->id
= $cachedcap->id
;
3218 $updatecap->riskbitmask
= $filecaps[$cachedcap->name
]['riskbitmask'];
3219 if (!update_record('capabilities', $updatecap)) {
3224 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name
])) {
3225 $filecaps[$cachedcap->name
]['contextlevel'] = 0; // no context level defined
3227 if ($cachedcap->contextlevel
!= $filecaps[$cachedcap->name
]['contextlevel']) {
3228 $updatecap = new object();
3229 $updatecap->id
= $cachedcap->id
;
3230 $updatecap->contextlevel
= $filecaps[$cachedcap->name
]['contextlevel'];
3231 if (!update_record('capabilities', $updatecap)) {
3239 // Are there new capabilities in the file definition?
3242 foreach ($filecaps as $filecap => $def) {
3244 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3245 if (!array_key_exists('riskbitmask', $def)) {
3246 $def['riskbitmask'] = 0; // no risk if not specified
3248 $newcaps[$filecap] = $def;
3251 // Add new capabilities to the stored definition.
3252 foreach ($newcaps as $capname => $capdef) {
3253 $capability = new object;
3254 $capability->name
= $capname;
3255 $capability->captype
= $capdef['captype'];
3256 $capability->contextlevel
= $capdef['contextlevel'];
3257 $capability->component
= $component;
3258 $capability->riskbitmask
= $capdef['riskbitmask'];
3260 if (!insert_record('capabilities', $capability, false, 'id')) {
3265 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3266 if ($rolecapabilities = get_records('role_capabilities', 'capability', $capdef['clonepermissionsfrom'])){
3267 foreach ($rolecapabilities as $rolecapability){
3268 //assign_capability will update rather than insert if capability exists
3269 if (!assign_capability($capname, $rolecapability->permission
,
3270 $rolecapability->roleid
, $rolecapability->contextid
, true)){
3271 notify('Could not clone capabilities for '.$capname);
3275 // Do we need to assign the new capabilities to roles that have the
3276 // legacy capabilities moodle/legacy:* as well?
3277 // we ignore legacy key if we have cloned permissions
3278 } else if (isset($capdef['legacy']) && is_array($capdef['legacy']) &&
3279 !assign_legacy_capabilities($capname, $capdef['legacy'])) {
3280 notify('Could not assign legacy capabilities for '.$capname);
3283 // Are there any capabilities that have been removed from the file
3284 // definition that we need to delete from the stored capabilities and
3285 // role assignments?
3286 capabilities_cleanup($component, $filecaps);
3293 * Deletes cached capabilities that are no longer needed by the component.
3294 * Also unassigns these capabilities from any roles that have them.
3295 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3296 * @param $newcapdef - array of the new capability definitions that will be
3297 * compared with the cached capabilities
3298 * @return int - number of deprecated capabilities that have been removed
3300 function capabilities_cleanup($component, $newcapdef=NULL) {
3304 if ($cachedcaps = get_cached_capabilities($component)) {
3305 foreach ($cachedcaps as $cachedcap) {
3306 if (empty($newcapdef) ||
3307 array_key_exists($cachedcap->name
, $newcapdef) === false) {
3309 // Remove from capabilities cache.
3310 if (!delete_records('capabilities', 'name', $cachedcap->name
)) {
3311 error('Could not delete deprecated capability '.$cachedcap->name
);
3315 // Delete from roles.
3316 if($roles = get_roles_with_capability($cachedcap->name
)) {
3317 foreach($roles as $role) {
3318 if (!unassign_capability($cachedcap->name
, $role->id
)) {
3319 error('Could not unassign deprecated capability '.
3320 $cachedcap->name
.' from role '.$role->name
);
3327 return $removedcount;
3338 * prints human readable context identifier.
3340 function print_context_name($context, $withprefix = true, $short = false) {
3343 switch ($context->contextlevel
) {
3345 case CONTEXT_SYSTEM
: // by now it's a definite an inherit
3346 $name = get_string('coresystem');
3350 if ($user = get_record('user', 'id', $context->instanceid
)) {
3352 $name = get_string('user').': ';
3354 $name .= fullname($user);
3358 case CONTEXT_COURSECAT
: // Coursecat -> coursecat or site
3359 if ($category = get_record('course_categories', 'id', $context->instanceid
)) {
3361 $name = get_string('category').': ';
3363 $name .=format_string($category->name
);
3367 case CONTEXT_COURSE
: // 1 to 1 to course cat
3368 if ($context->instanceid
== SITEID
) {
3369 $name = get_string('frontpage', 'admin');
3371 if ($course = get_record('course', 'id', $context->instanceid
)) {
3373 $name = get_string('course').': ';
3376 $name .= format_string($course->shortname
);
3378 $name .= format_string($course->fullname
);
3384 case CONTEXT_GROUP
: // 1 to 1 to course
3385 if ($name = groups_get_group_name($context->instanceid
)) {
3387 $name = get_string('group').': '. $name;
3392 case CONTEXT_MODULE
: // 1 to 1 to course
3393 if ($cm = get_record('course_modules','id',$context->instanceid
)) {
3394 if ($module = get_record('modules','id',$cm->module
)) {
3395 if ($mod = get_record($module->name
, 'id', $cm->instance
)) {
3397 $name = get_string('activitymodule').': ';
3399 $name .= $mod->name
;
3405 case CONTEXT_BLOCK
: // not necessarily 1 to 1 to course
3406 if ($blockinstance = get_record('block_instance','id',$context->instanceid
)) {
3407 if ($block = get_record('block','id',$blockinstance->blockid
)) {
3409 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3410 require_once("$CFG->dirroot/blocks/$block->name/block_$block->name.php");
3411 $blockname = "block_$block->name";
3412 if ($blockobject = new $blockname()) {
3414 $name = get_string('block').': ';
3416 $name .= $blockobject->title
;
3423 error ('This is an unknown context (' . $context->contextlevel
. ') in print_context_name!');
3432 * Extracts the relevant capabilities given a contextid.
3433 * All case based, example an instance of forum context.
3434 * Will fetch all forum related capabilities, while course contexts
3435 * Will fetch all capabilities
3436 * @param object context
3440 * `name` varchar(150) NOT NULL,
3441 * `captype` varchar(50) NOT NULL,
3442 * `contextlevel` int(10) NOT NULL,
3443 * `component` varchar(100) NOT NULL,
3445 function fetch_context_capabilities($context) {
3449 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
3451 switch ($context->contextlevel
) {
3453 case CONTEXT_SYSTEM
: // all
3455 FROM {$CFG->prefix}capabilities";
3459 $extracaps = array('moodle/grade:viewall');
3460 foreach ($extracaps as $key=>$value) {
3461 $extracaps[$key]= "'$value'";
3463 $extra = implode(',', $extracaps);
3465 FROM {$CFG->prefix}capabilities
3466 WHERE contextlevel = ".CONTEXT_USER
."
3467 OR name IN ($extra)";
3470 case CONTEXT_COURSECAT
: // course category context and bellow
3472 FROM {$CFG->prefix}capabilities
3473 WHERE contextlevel IN (".CONTEXT_COURSECAT
.",".CONTEXT_COURSE
.",".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")";
3476 case CONTEXT_COURSE
: // course context and bellow
3478 FROM {$CFG->prefix}capabilities
3479 WHERE contextlevel IN (".CONTEXT_COURSE
.",".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")";
3482 case CONTEXT_MODULE
: // mod caps
3483 $cm = get_record('course_modules', 'id', $context->instanceid
);
3484 $module = get_record('modules', 'id', $cm->module
);
3487 $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
3488 if (file_exists($modfile)) {
3489 include_once($modfile);
3490 $modfunction = $module->name
.'_get_extra_capabilities';
3491 if (function_exists($modfunction)) {
3492 if ($extracaps = $modfunction()) {
3493 foreach ($extracaps as $key=>$value) {
3494 $extracaps[$key]= "'$value'";
3496 $extra = implode(',', $extracaps);
3497 $extra = "OR name IN ($extra)";
3503 FROM {$CFG->prefix}capabilities
3504 WHERE (contextlevel = ".CONTEXT_MODULE
."
3505 AND component = 'mod/$module->name')
3509 case CONTEXT_BLOCK
: // block caps
3510 $cb = get_record('block_instance', 'id', $context->instanceid
);
3511 $block = get_record('block', 'id', $cb->blockid
);
3514 if ($blockinstance = block_instance($block->name
)) {
3515 if ($extracaps = $blockinstance->get_extra_capabilities()) {
3516 foreach ($extracaps as $key=>$value) {
3517 $extracaps[$key]= "'$value'";
3519 $extra = implode(',', $extracaps);
3520 $extra = "OR name IN ($extra)";
3525 FROM {$CFG->prefix}capabilities
3526 WHERE (contextlevel = ".CONTEXT_BLOCK
."
3527 AND component = 'block/$block->name')
3535 if (!$records = get_records_sql($SQL.' '.$sort)) {
3544 * This function pulls out all the resolved capabilities (overrides and
3545 * defaults) of a role used in capability overrides in contexts at a given
3547 * @param obj $context
3548 * @param int $roleid
3549 * @param bool self - if set to true, resolve till this level, else stop at immediate parent level
3552 function role_context_capabilities($roleid, $context, $cap='') {
3555 $contexts = get_parent_contexts($context);
3556 $contexts[] = $context->id
;
3557 $contexts = '('.implode(',', $contexts).')';
3560 $search = " AND rc.capability = '$cap' ";
3566 FROM {$CFG->prefix}role_capabilities rc,
3567 {$CFG->prefix}context c
3568 WHERE rc.contextid in $contexts
3569 AND rc.roleid = $roleid
3570 AND rc.contextid = c.id $search
3571 ORDER BY c.contextlevel DESC,
3572 rc.capability DESC";
3574 $capabilities = array();
3576 if ($records = get_records_sql($SQL)) {
3577 // We are traversing via reverse order.
3578 foreach ($records as $record) {
3579 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
3580 if (!isset($capabilities[$record->capability
]) ||
$record->permission
<-500) {
3581 $capabilities[$record->capability
] = $record->permission
;
3585 return $capabilities;
3589 * Recursive function which, given a context, find all parent context ids,
3590 * and return the array in reverse order, i.e. parent first, then grand
3593 * @param object $context
3596 function get_parent_contexts($context) {
3598 if ($context->path
== '') {
3602 $parentcontexts = substr($context->path
, 1); // kill leading slash
3603 $parentcontexts = explode('/', $parentcontexts);
3604 array_pop($parentcontexts); // and remove its own id
3606 return array_reverse($parentcontexts);
3610 * Return the id of the parent of this context, or false if there is no parent (only happens if this
3611 * is the site context.)
3613 * @param object $context
3614 * @return integer the id of the parent context.
3616 function get_parent_contextid($context) {
3617 $parentcontexts = get_parent_contexts($context);
3618 if (count($parentcontexts) == 0) {
3621 return array_shift($parentcontexts);
3625 * Recursive function which, given a context, find all its children context ids.
3627 * When called for a course context, it will return the modules and blocks
3628 * displayed in the course page.
3630 * For course category contexts it will return categories and courses. It will
3631 * NOT recurse into courses - if you want to do that, call it on the returned
3634 * If called on a course context it _will_ populate the cache with the appropriate
3637 * @param object $context.
3638 * @return array of child records
3640 function get_child_contexts($context) {
3642 global $CFG, $context_cache;
3644 // We *MUST* populate the context_cache as the callers
3645 // will probably ask for the full record anyway soon after
3646 // soon after calling us ;-)
3648 switch ($context->contextlevel
) {
3655 case CONTEXT_MODULE
:
3665 case CONTEXT_COURSE
:
3667 // - module instances - easy
3669 // - blocks assigned to the course-view page explicitly - easy
3670 // - blocks pinned (note! we get all of them here, regardless of vis)
3671 $sql = " SELECT ctx.*
3672 FROM {$CFG->prefix}context ctx
3673 WHERE ctx.path LIKE '{$context->path}/%'
3674 AND ctx.contextlevel IN (".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")
3677 FROM {$CFG->prefix}context ctx
3678 JOIN {$CFG->prefix}groups g
3679 ON (ctx.instanceid=g.id AND ctx.contextlevel=".CONTEXT_GROUP
.")
3680 WHERE g.courseid={$context->instanceid}
3683 FROM {$CFG->prefix}context ctx
3684 JOIN {$CFG->prefix}block_pinned b
3685 ON (ctx.instanceid=b.blockid AND ctx.contextlevel=".CONTEXT_BLOCK
.")
3686 WHERE b.pagetype='course-view'
3688 $rs = get_recordset_sql($sql);
3690 while ($rec = rs_fetch_next_record($rs)) {
3691 $records[$rec->id
] = $rec;
3692 $context_cache[$rec->contextlevel
][$rec->instanceid
] = $rec;
3698 case CONTEXT_COURSECAT
:
3702 $sql = " SELECT ctx.*
3703 FROM {$CFG->prefix}context ctx
3704 WHERE ctx.path LIKE '{$context->path}/%'
3705 AND ctx.contextlevel IN (".CONTEXT_COURSECAT
.",".CONTEXT_COURSE
.")
3707 $rs = get_recordset_sql($sql);
3709 while ($rec = rs_fetch_next_record($rs)) {
3710 $records[$rec->id
] = $rec;
3711 $context_cache[$rec->contextlevel
][$rec->instanceid
] = $rec;
3722 case CONTEXT_SYSTEM
:
3723 // Just get all the contexts except for CONTEXT_SYSTEM level
3724 // and hope we don't OOM in the process - don't cache
3725 $sql = 'SELECT c.*'.
3726 'FROM '.$CFG->prefix
.'context c '.
3727 'WHERE contextlevel != '.CONTEXT_SYSTEM
;
3729 return get_records_sql($sql);
3733 error('This is an unknown context (' . $context->contextlevel
. ') in get_child_contexts!');
3740 * Gets a string for sql calls, searching for stuff in this context or above
3741 * @param object $context
3744 function get_related_contexts_string($context) {
3745 if ($parents = get_parent_contexts($context)) {
3746 return (' IN ('.$context->id
.','.implode(',', $parents).')');
3748 return (' ='.$context->id
);
3753 * Returns the human-readable, translated version of the capability.
3754 * Basically a big switch statement.
3755 * @param $capabilityname - e.g. mod/choice:readresponses
3757 function get_capability_string($capabilityname) {
3759 // Typical capabilityname is mod/choice:readresponses
3761 $names = split('/', $capabilityname);
3762 $stringname = $names[1]; // choice:readresponses
3763 $components = split(':', $stringname);
3764 $componentname = $components[0]; // choice
3766 switch ($names[0]) {
3768 $string = get_string($stringname, $componentname);
3772 $string = get_string($stringname, 'block_'.$componentname);
3776 if ($componentname == 'local') {
3777 $string = get_string($stringname, 'local');
3779 $string = get_string($stringname, 'role');
3784 $string = get_string($stringname, 'enrol_'.$componentname);
3788 $string = get_string($stringname, 'format_'.$componentname);
3792 $string = get_string($stringname, 'gradeexport_'.$componentname);
3796 $string = get_string($stringname, 'gradeimport_'.$componentname);
3800 $string = get_string($stringname, 'gradereport_'.$componentname);
3804 $string = get_string($stringname);
3813 * This gets the mod/block/course/core etc strings.
3815 * @param $contextlevel
3817 function get_component_string($component, $contextlevel) {
3819 switch ($contextlevel) {
3821 case CONTEXT_SYSTEM
:
3822 if (preg_match('|^enrol/|', $component)) {
3823 $langname = str_replace('/', '_', $component);
3824 $string = get_string('enrolname', $langname);
3825 } else if (preg_match('|^block/|', $component)) {
3826 $langname = str_replace('/', '_', $component);
3827 $string = get_string('blockname', $langname);
3828 } else if (preg_match('|^local|', $component)) {
3829 $langname = str_replace('/', '_', $component);
3830 $string = get_string('local');
3832 $string = get_string('coresystem');
3837 $string = get_string('users');
3840 case CONTEXT_COURSECAT
:
3841 $string = get_string('categories');
3844 case CONTEXT_COURSE
:
3845 if (preg_match('|^gradeimport/|', $component)
3846 ||
preg_match('|^gradeexport/|', $component)
3847 ||
preg_match('|^gradereport/|', $component)) {
3848 $string = get_string('gradebook', 'admin');
3850 $string = get_string('course');
3855 $string = get_string('group');
3858 case CONTEXT_MODULE
:
3859 $string = get_string('modulename', basename($component));
3863 if( $component == 'moodle' ){
3864 $string = get_string('block');
3866 $string = get_string('blockname', 'block_'.basename($component));
3871 error ('This is an unknown context $contextlevel (' . $contextlevel . ') in get_component_string!');
3879 * Gets the list of roles assigned to this context and up (parents)
3880 * @param object $context
3881 * @param view - set to true when roles are pulled for display only
3882 * this is so that we can filter roles with no visible
3883 * assignment, for example, you might want to "hide" all
3884 * course creators when browsing the course participants
3888 function get_roles_used_in_context($context, $view = false) {
3892 // filter for roles with all hidden assignments
3893 // no need to return when only pulling roles for reviewing
3894 // e.g. participants page.
3895 $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))?
' AND ra.hidden = 0 ':'';
3896 $contextlist = get_related_contexts_string($context);
3898 $sql = "SELECT DISTINCT r.id,
3902 FROM {$CFG->prefix}role_assignments ra,
3903 {$CFG->prefix}role r
3904 WHERE r.id = ra.roleid
3905 AND ra.contextid $contextlist
3907 ORDER BY r.sortorder ASC";
3909 return get_records_sql($sql);
3913 * This function is used to print roles column in user profile page.
3915 * @param object context
3918 function get_user_roles_in_context($userid, $context, $view=true){
3922 $SQL = 'select * from '.$CFG->prefix
.'role_assignments ra, '.$CFG->prefix
.'role r where ra.userid='.$userid.' and ra.contextid='.$context->id
.' and ra.roleid = r.id';
3923 $rolenames = array();
3924 if ($roles = get_records_sql($SQL)) {
3925 foreach ($roles as $userrole) {
3926 // MDL-12544, if we are in view mode and current user has no capability to view hidden assignment, skip it
3927 if ($userrole->hidden
&& $view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
3930 $rolenames[$userrole->roleid
] = $userrole->name
;
3933 $rolenames = role_fix_names($rolenames, $context); // Substitute aliases
3935 foreach ($rolenames as $roleid => $rolename) {
3936 $rolenames[$roleid] = '<a href="'.$CFG->wwwroot
.'/user/index.php?contextid='.$context->id
.'&roleid='.$roleid.'">'.$rolename.'</a>';
3938 $rolestring = implode(',', $rolenames);
3945 * Checks if a user can override capabilities of a particular role in this context
3946 * @param object $context
3947 * @param int targetroleid - the id of the role you want to override
3950 function user_can_override($context, $targetroleid) {
3952 // TODO: not needed anymore, remove in 2.0
3954 // first check if user has override capability
3955 // if not return false;
3956 if (!has_capability('moodle/role:override', $context)) {
3959 // pull out all active roles of this user from this context(or above)
3960 if ($userroles = get_user_roles($context)) {
3961 foreach ($userroles as $userrole) {
3962 // if any in the role_allow_override table, then it's ok
3963 if (get_record('role_allow_override', 'roleid', $userrole->roleid
, 'allowoverride', $targetroleid)) {
3974 * Checks if a user can assign users to a particular role in this context
3975 * @param object $context
3976 * @param int targetroleid - the id of the role you want to assign users to
3979 function user_can_assign($context, $targetroleid) {
3981 // first check if user has override capability
3982 // if not return false;
3983 if (!has_capability('moodle/role:assign', $context)) {
3986 // pull out all active roles of this user from this context(or above)
3987 if ($userroles = get_user_roles($context)) {
3988 foreach ($userroles as $userrole) {
3989 // if any in the role_allow_override table, then it's ok
3990 if (get_record('role_allow_assign', 'roleid', $userrole->roleid
, 'allowassign', $targetroleid)) {
3999 /** Returns all site roles in correct sort order.
4002 function get_all_roles() {
4003 return get_records('role', '', '', 'sortorder ASC');
4007 * gets all the user roles assigned in this context, or higher contexts
4008 * this is mainly used when checking if a user can assign a role, or overriding a role
4009 * i.e. we need to know what this user holds, in order to verify against allow_assign and
4010 * allow_override tables
4011 * @param object $context
4012 * @param int $userid
4013 * @param view - set to true when roles are pulled for display only
4014 * this is so that we can filter roles with no visible
4015 * assignment, for example, you might want to "hide" all
4016 * course creators when browsing the course participants
4020 function get_user_roles($context, $userid=0, $checkparentcontexts=true, $order='c.contextlevel DESC, r.sortorder ASC', $view=false) {
4022 global $USER, $CFG, $db;
4024 if (empty($userid)) {
4025 if (empty($USER->id
)) {
4028 $userid = $USER->id
;
4030 // set up hidden sql
4031 $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))?
' AND ra.hidden = 0 ':'';
4033 if ($checkparentcontexts && ($parents = get_parent_contexts($context))) {
4034 $contexts = ' ra.contextid IN ('.implode(',' , $parents).','.$context->id
.')';
4036 $contexts = ' ra.contextid = \''.$context->id
.'\'';
4039 if (!$return = get_records_sql('SELECT ra.*, r.name, r.shortname
4040 FROM '.$CFG->prefix
.'role_assignments ra,
4041 '.$CFG->prefix
.'role r,
4042 '.$CFG->prefix
.'context c
4043 WHERE ra.userid = '.$userid.'
4044 AND ra.roleid = r.id
4045 AND ra.contextid = c.id
4046 AND '.$contexts . $hiddensql .'
4047 ORDER BY '.$order)) {
4055 * Creates a record in the allow_override table
4056 * @param int sroleid - source roleid
4057 * @param int troleid - target roleid
4058 * @return int - id or false
4060 function allow_override($sroleid, $troleid) {
4061 $record = new object();
4062 $record->roleid
= $sroleid;
4063 $record->allowoverride
= $troleid;
4064 return insert_record('role_allow_override', $record);
4068 * Creates a record in the allow_assign table
4069 * @param int sroleid - source roleid
4070 * @param int troleid - target roleid
4071 * @return int - id or false
4073 function allow_assign($sroleid, $troleid) {
4074 $record = new object;
4075 $record->roleid
= $sroleid;
4076 $record->allowassign
= $troleid;
4077 return insert_record('role_allow_assign', $record);
4081 * Gets a list of roles that this user can assign in this context
4082 * @param object $context
4083 * @param string $field
4084 * @param int $rolenamedisplay
4087 function get_assignable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4090 if (!has_capability('moodle/role:assign', $context)) {
4094 $parents = get_parent_contexts($context);
4095 $parents[] = $context->id
;
4096 $contexts = implode(',' , $parents);
4098 if (!$roles = get_records_sql("SELECT ro.*
4099 FROM {$CFG->prefix}role ro,
4101 SELECT DISTINCT r.id
4102 FROM {$CFG->prefix}role r,
4103 {$CFG->prefix}role_assignments ra,
4104 {$CFG->prefix}role_allow_assign raa
4105 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4106 AND raa.roleid = ra.roleid AND r.id = raa.allowassign
4108 WHERE ro.id = inline_view.id
4109 ORDER BY ro.sortorder ASC")) {
4113 foreach ($roles as $role) {
4114 $roles[$role->id
] = $role->$field;
4117 return role_fix_names($roles, $context, $rolenamedisplay);
4121 * Gets a list of roles that this user can assign in this context, for the switchrole menu
4123 * @param object $context
4124 * @param string $field
4125 * @param int $rolenamedisplay
4128 function get_assignable_roles_for_switchrole($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4131 if (!has_capability('moodle/role:assign', $context)) {
4135 $parents = get_parent_contexts($context);
4136 $parents[] = $context->id
;
4137 $contexts = implode(',' , $parents);
4139 if (!$roles = get_records_sql("SELECT ro.*
4140 FROM {$CFG->prefix}role ro,
4142 SELECT DISTINCT r.id
4143 FROM {$CFG->prefix}role r,
4144 {$CFG->prefix}role_assignments ra,
4145 {$CFG->prefix}role_allow_assign raa,
4146 {$CFG->prefix}role_capabilities rc
4147 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4148 AND raa.roleid = ra.roleid AND r.id = raa.allowassign
4149 AND r.id = rc.roleid AND rc.capability = 'moodle/course:view' AND rc.capability != 'moodle/site:doanything'
4151 WHERE ro.id = inline_view.id
4152 ORDER BY ro.sortorder ASC")) {
4156 foreach ($roles as $role) {
4157 $roles[$role->id
] = $role->$field;
4160 return role_fix_names($roles, $context, $rolenamedisplay);
4164 * Gets a list of roles that this user can override or safeoverride in this context
4165 * @param object $context
4166 * @param string $field
4167 * @param int $rolenamedisplay
4170 function get_overridable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4173 if (!has_capability('moodle/role:override', $context) and !has_capability('moodle/role:safeoverride', $context)) {
4177 $parents = get_parent_contexts($context);
4178 $parents[] = $context->id
;
4179 $contexts = implode(',' , $parents);
4181 if (!$roles = get_records_sql("SELECT ro.*
4182 FROM {$CFG->prefix}role ro,
4184 SELECT DISTINCT r.id
4185 FROM {$CFG->prefix}role r,
4186 {$CFG->prefix}role_assignments ra,
4187 {$CFG->prefix}role_allow_override rao
4188 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4189 AND rao.roleid = ra.roleid AND r.id = rao.allowoverride
4191 WHERE ro.id = inline_view.id
4192 ORDER BY ro.sortorder ASC")) {
4196 foreach ($roles as $role) {
4197 $roles[$role->id
] = $role->$field;
4200 return role_fix_names($roles, $context, $rolenamedisplay);
4204 * Returns a role object that is the default role for new enrolments
4207 * @param object $course
4208 * @return object $role
4210 function get_default_course_role($course) {
4213 /// First let's take the default role the course may have
4214 if (!empty($course->defaultrole
)) {
4215 if ($role = get_record('role', 'id', $course->defaultrole
)) {
4220 /// Otherwise the site setting should tell us
4221 if ($CFG->defaultcourseroleid
) {
4222 if ($role = get_record('role', 'id', $CFG->defaultcourseroleid
)) {
4227 /// It's unlikely we'll get here, but just in case, try and find a student role
4228 if ($studentroles = get_roles_with_capability('moodle/legacy:student', CAP_ALLOW
)) {
4229 return array_shift($studentroles); /// Take the first one
4237 * Who has this capability in this context?
4239 * This can be a very expensive call - use sparingly and keep
4240 * the results if you are going to need them again soon.
4242 * Note if $fields is empty this function attempts to get u.*
4243 * which can get rather large - and has a serious perf impact
4246 * @param $context - object
4247 * @param $capability - string capability
4248 * @param $fields - fields to be pulled
4249 * @param $sort - the sort order
4250 * @param $limitfrom - number of records to skip (offset)
4251 * @param $limitnum - number of records to fetch
4252 * @param $groups - single group or array of groups - only return
4253 * users who are in one of these group(s).
4254 * @param $exceptions - list of users to exclude
4255 * @param view - set to true when roles are pulled for display only
4256 * this is so that we can filter roles with no visible
4257 * assignment, for example, you might want to "hide" all
4258 * course creators when browsing the course participants
4260 * @param boolean $useviewallgroups if $groups is set the return users who
4261 * have capability both $capability and moodle/site:accessallgroups
4262 * in this context, as well as users who have $capability and who are
4265 function get_users_by_capability($context, $capability, $fields='', $sort='',
4266 $limitfrom='', $limitnum='', $groups='', $exceptions='', $doanything=true,
4267 $view=false, $useviewallgroups=false) {
4270 $ctxids = substr($context->path
, 1); // kill leading slash
4271 $ctxids = str_replace('/', ',', $ctxids);
4273 // Context is the frontpage
4274 $isfrontpage = false;
4275 $iscoursepage = false; // coursepage other than fp
4276 if ($context->contextlevel
== CONTEXT_COURSE
) {
4277 if ($context->instanceid
== SITEID
) {
4278 $isfrontpage = true;
4280 $iscoursepage = true;
4284 // What roles/rolecaps are interesting?
4285 $caps = "'$capability'";
4286 if ($doanything===true) {
4287 $caps.=",'moodle/site:doanything'";
4288 $doanything_join='';
4289 $doanything_cond='';
4291 // This is an outer join against
4292 // admin-ish roleids. Any row that succeeds
4293 // in JOINing here ends up removed from
4294 // the resultset. This means we remove
4295 // rolecaps from roles that also have
4296 // 'doanything' capabilities.
4297 $doanything_join="LEFT OUTER JOIN (
4298 SELECT DISTINCT rc.roleid
4299 FROM {$CFG->prefix}role_capabilities rc
4300 WHERE rc.capability='moodle/site:doanything'
4301 AND rc.permission=".CAP_ALLOW
."
4302 AND rc.contextid IN ($ctxids)
4304 ON rc.roleid=dar.roleid";
4305 $doanything_cond="AND dar.roleid IS NULL";
4308 // fetch all capability records - we'll walk several
4309 // times over them, and should be a small set
4311 $negperm = false; // has any negative (<0) permission?
4314 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability,
4315 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel
4316 FROM {$CFG->prefix}role_capabilities rc
4317 JOIN {$CFG->prefix}context ctx on rc.contextid = ctx.id
4319 WHERE rc.capability IN ($caps) AND ctx.id IN ($ctxids)
4321 ORDER BY rc.roleid ASC, ctx.depth ASC";
4322 if ($capdefs = get_records_sql($sql)) {
4323 foreach ($capdefs AS $rcid=>$rc) {
4324 $roleids[] = (int)$rc->roleid
;
4325 if ($rc->permission
< 0) {
4331 $roleids = array_unique($roleids);
4333 if (count($roleids)===0) { // noone here!
4337 // is the default role interesting? does it have
4338 // a relevant rolecap? (we use this a lot later)
4339 if (in_array((int)$CFG->defaultuserroleid
, $roleids, true)) {
4340 $defaultroleinteresting = true;
4342 $defaultroleinteresting = false;
4346 // Prepare query clauses
4348 $wherecond = array();
4350 // Non-deleted users. We never return deleted users.
4351 $wherecond['nondeleted'] = 'u.deleted = 0';
4355 if (is_array($groups)) {
4356 $grouptest = 'gm.groupid IN (' . implode(',', $groups) . ')';
4358 $grouptest = 'gm.groupid = ' . $groups;
4360 $grouptest = 'ra.userid IN (SELECT userid FROM ' .
4361 $CFG->prefix
. 'groups_members gm WHERE ' . $grouptest . ')';
4363 if ($useviewallgroups) {
4364 $viewallgroupsusers = get_users_by_capability($context,
4365 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
4366 $wherecond['groups'] = '('. $grouptest . ' OR ra.userid IN (' .
4367 implode(',', array_keys($viewallgroupsusers)) . '))';
4369 $wherecond['groups'] = '(' . $grouptest .')';
4374 if (!empty($exceptions)) {
4375 $wherecond['userexceptions'] = ' u.id NOT IN ('.$exceptions.')';
4378 /// Set up hidden role-assignments sql
4379 if ($view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
4380 $condhiddenra = 'AND ra.hidden = 0 ';
4381 $sscondhiddenra = 'AND ssra.hidden = 0 ';
4384 $sscondhiddenra = '';
4387 // Collect WHERE conditions
4388 $where = implode(' AND ', array_values($wherecond));
4390 $where = 'WHERE ' . $where;
4393 /// Set up default fields
4394 if (empty($fields)) {
4395 if ($iscoursepage) {
4396 $fields = 'u.*, ul.timeaccess as lastaccess';
4402 /// Set up default sort
4403 if (empty($sort)) { // default to course lastaccess or just lastaccess
4404 if ($iscoursepage) {
4405 $sort = 'ul.timeaccess';
4407 $sort = 'u.lastaccess';
4410 $sortby = $sort ?
" ORDER BY $sort " : '';
4412 // User lastaccess JOIN
4413 if ((strpos($sort, 'ul.timeaccess') === FALSE) and (strpos($fields, 'ul.timeaccess') === FALSE)) { // user_lastaccess is not required MDL-13810
4416 $uljoin = "LEFT OUTER JOIN {$CFG->prefix}user_lastaccess ul
4417 ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
4421 // Simple cases - No negative permissions means we can take shortcuts
4425 // at the frontpage, and all site users have it - easy!
4426 if ($isfrontpage && !empty($CFG->defaultfrontpageroleid
)
4427 && in_array((int)$CFG->defaultfrontpageroleid
, $roleids, true)) {
4429 return get_records_sql("SELECT $fields
4430 FROM {$CFG->prefix}user u
4433 $limitfrom, $limitnum);
4436 // all site users have it, anyway
4437 // TODO: NOT ALWAYS! Check this case because this gets run for cases like this:
4438 // 1) Default role has the permission for a module thing like mod/choice:choose
4439 // 2) We are checking for an activity module context in a course
4440 // 3) Thus all users are returned even though course:view is also required
4441 if ($defaultroleinteresting) {
4442 $sql = "SELECT $fields
4443 FROM {$CFG->prefix}user u
4447 return get_records_sql($sql, $limitfrom, $limitnum);
4450 /// Simple SQL assuming no negative rolecaps.
4451 /// We use a subselect to grab the role assignments
4452 /// ensuring only one row per user -- even if they
4453 /// have many "relevant" role assignments.
4454 $select = " SELECT $fields";
4455 $from = " FROM {$CFG->prefix}user u
4456 JOIN (SELECT DISTINCT ssra.userid
4457 FROM {$CFG->prefix}role_assignments ssra
4458 WHERE ssra.contextid IN ($ctxids)
4459 AND ssra.roleid IN (".implode(',',$roleids) .")
4461 ) ra ON ra.userid = u.id
4463 return get_records_sql($select.$from.$where.$sortby, $limitfrom, $limitnum);
4467 // If there are any negative rolecaps, we need to
4468 // work through a subselect that will bring several rows
4469 // per user (one per RA).
4470 // Since we cannot do the job in pure SQL (not without SQL stored
4471 // procedures anyway), we end up tied to processing the data in PHP
4472 // all the way down to pagination.
4474 // In some cases, this will mean bringing across a ton of data --
4475 // when paginating, we have to walk the permisisons of all the rows
4476 // in the _previous_ pages to get the pagination correct in the case
4477 // of users that end up not having the permission - this removed.
4480 // Prepare the role permissions datastructure for fast lookups
4481 $roleperms = array(); // each role cap and depth
4482 foreach ($capdefs AS $rcid=>$rc) {
4484 $rid = (int)$rc->roleid
;
4485 $perm = (int)$rc->permission
;
4486 $rcdepth = (int)$rc->ctxdepth
;
4487 if (!isset($roleperms[$rc->capability
][$rid])) {
4488 $roleperms[$rc->capability
][$rid] = (object)array('perm' => $perm,
4489 'rcdepth' => $rcdepth);
4491 if ($roleperms[$rc->capability
][$rid]->perm
== CAP_PROHIBIT
) {
4494 // override - as we are going
4495 // from general to local perms
4496 // (as per the ORDER BY...depth ASC above)
4497 // and local perms win...
4498 $roleperms[$rc->capability
][$rid] = (object)array('perm' => $perm,
4499 'rcdepth' => $rcdepth);
4504 if ($context->contextlevel
== CONTEXT_SYSTEM
4506 ||
$defaultroleinteresting) {
4508 // Handle system / sitecourse / defaultrole-with-perhaps-neg-overrides
4509 // with a SELECT FROM user LEFT OUTER JOIN against ra -
4510 // This is expensive on the SQL and PHP sides -
4511 // moves a ton of data across the wire.
4512 $ss = "SELECT u.id as userid, ra.roleid,
4514 FROM {$CFG->prefix}user u
4515 LEFT OUTER JOIN {$CFG->prefix}role_assignments ra
4516 ON (ra.userid = u.id
4517 AND ra.contextid IN ($ctxids)
4518 AND ra.roleid IN (".implode(',',$roleids) .")
4520 LEFT OUTER JOIN {$CFG->prefix}context ctx
4521 ON ra.contextid=ctx.id
4524 // "Normal complex case" - the rolecaps we are after will
4525 // be defined in a role assignment somewhere.
4526 $ss = "SELECT ra.userid as userid, ra.roleid,
4528 FROM {$CFG->prefix}role_assignments ra
4529 JOIN {$CFG->prefix}context ctx
4530 ON ra.contextid=ctx.id
4531 WHERE ra.contextid IN ($ctxids)
4533 AND ra.roleid IN (".implode(',',$roleids) .")";
4536 $select = "SELECT $fields ,ra.roleid, ra.depth ";
4537 $from = "FROM ($ss) ra
4538 JOIN {$CFG->prefix}user u
4542 // Each user's entries MUST come clustered together
4543 // and RAs ordered in depth DESC - the role/cap resolution
4544 // code depends on this.
4545 $sort .= ' , ra.userid ASC, ra.depth DESC';
4546 $sortby .= ' , ra.userid ASC, ra.depth DESC ';
4548 $rs = get_recordset_sql($select.$from.$where.$sortby);
4551 // Process the user accounts+RAs, folding repeats together...
4553 // The processing for this recordset is tricky - to fold
4554 // the role/perms of users with multiple role-assignments
4555 // correctly while still processing one-row-at-a-time
4556 // we need to add a few additional 'private' fields to
4557 // the results array - so we can treat the rows as a
4558 // state machine to track the cap/perms and at what RA-depth
4559 // and RC-depth they were defined.
4561 // So what we do here is:
4562 // - loop over rows, checking pagination limits
4563 // - when we find a new user, if we are in the page add it to the
4564 // $results, and start building $ras array with its role-assignments
4565 // - when we are dealing with the next user, or are at the end of the userlist
4566 // (last rec or last in page), trigger the check-permission idiom
4567 // - the check permission idiom will
4568 // - add the default enrolment if needed
4569 // - call has_capability_from_rarc(), which based on RAs and RCs will return a bool
4570 // (should be fairly tight code ;-) )
4571 // - if the user has permission, all is good, just $c++ (counter)
4572 // - ...else, decrease the counter - so pagination is kept straight,
4573 // and (if we are in the page) remove from the results
4577 // pagination controls
4579 $limitfrom = (int)$limitfrom;
4580 $limitnum = (int)$limitnum;
4583 // Track our last user id so we know when we are dealing
4584 // with a new user...
4589 // $ras: role assignments, multidimensional array
4590 // treat as a stack - going from local to general
4591 // $ras = (( roleid=> x, $depth=>y) , ( roleid=> x, $depth=>y))
4593 while ($user = rs_fetch_next_record($rs)) {
4595 //error_log(" Record: " . print_r($user,1));
4598 // Pagination controls
4599 // Note that we might end up removing a user
4600 // that ends up _not_ having the rights,
4601 // therefore rolling back $c
4603 if ($lastuserid != $user->id
) {
4605 // Did the last user end up with a positive permission?
4606 if ($lastuserid !=0) {
4607 if ($defaultroleinteresting) {
4608 // add the role at the end of $ras
4609 $ras[] = array( 'roleid' => $CFG->defaultuserroleid
,
4612 if (has_capability_from_rarc($ras, $roleperms, $capability, $doanything)) {
4615 // remove the user from the result set,
4616 // only if we are 'in the page'
4617 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4618 unset($results[$lastuserid]);
4623 // Did we hit pagination limit?
4624 if ($limitnum !==0 && $c >= ($limitfrom+
$limitnum)) { // we are done!
4628 // New user setup, and $ras reset
4629 $lastuserid = $user->id
;
4631 if (!empty($user->roleid
)) {
4632 $ras[] = array( 'roleid' => (int)$user->roleid
,
4633 'depth' => (int)$user->depth
);
4636 // if we are 'in the page', also add the rec
4637 // to the results...
4638 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4639 $results[$user->id
] = $user; // trivial
4642 // Additional RA for $lastuserid
4643 $ras[] = array( 'roleid'=>(int)$user->roleid
,
4644 'depth'=>(int)$user->depth
);
4647 } // end while(fetch)
4649 // Prune last entry if necessary
4650 if ($lastuserid !=0) {
4651 if ($defaultroleinteresting) {
4652 // add the role at the end of $ras
4653 $ras[] = array( 'roleid' => $CFG->defaultuserroleid
,
4656 if (!has_capability_from_rarc($ras, $roleperms, $capability, $doanything)) {
4657 // remove the user from the result set,
4658 // only if we are 'in the page'
4659 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4660 if (isset($results[$lastuserid])) {
4661 unset($results[$lastuserid]);
4671 * Fast (fast!) utility function to resolve if a capability is granted,
4672 * based on Role Assignments and Role Capabilities.
4674 * Used (at least) by get_users_by_capability().
4676 * If PHP had fast built-in memoize functions, we could
4677 * add a $contextid parameter and memoize the return values.
4679 * @param array $ras - role assignments
4680 * @param array $roleperms - role permissions
4681 * @param string $capability - name of the capability
4682 * @param bool $doanything
4686 function has_capability_from_rarc($ras, $roleperms, $capability, $doanything) {
4687 // Mini-state machine, using $hascap
4688 // $hascap[ 'moodle/foo:bar' ]->perm = CAP_SOMETHING (numeric constant)
4689 // $hascap[ 'moodle/foo:bar' ]->radepth = depth of the role assignment that set it
4690 // $hascap[ 'moodle/foo:bar' ]->rcdepth = depth of the rolecap that set it
4691 // -- when resolving conflicts, we need to look into radepth first, if unresolved
4693 $caps = array($capability);
4695 $caps[] = 'moodle/site:candoanything';
4701 // Compute which permission/roleassignment/rolecap
4702 // wins for each capability we are walking
4704 foreach ($ras as $ra) {
4705 foreach ($caps as $cap) {
4706 if (!isset($roleperms[$cap][$ra['roleid']])) {
4707 // nothing set for this cap - skip
4710 // We explicitly clone here as we
4711 // add more properties to it
4712 // that must stay separate from the
4713 // original roleperm data structure
4714 $rp = clone($roleperms[$cap][$ra['roleid']]);
4715 $rp->radepth
= $ra['depth'];
4717 // Trivial case, we are the first to set
4718 if (!isset($hascap[$cap])) {
4719 $hascap[$cap] = $rp;
4723 // Resolve who prevails, in order of precendence
4724 // - Prohibits always wins
4729 if ($rp->perm
=== CAP_PROHIBIT
) {
4730 $hascap[$cap] = $rp;
4733 if ($hascap[$cap]->perm
=== CAP_PROHIBIT
) {
4737 // Locality of RA - the look is ordered by depth DESC
4738 // so from local to general -
4739 // Higher RA loses to local RA... unless perm===0
4740 /// Thanks to the order of the records, $rp->radepth <= $hascap[$cap]->radepth
4741 if ($rp->radepth
> $hascap[$cap]->radepth
) {
4742 error_log('Should not happen @ ' . __FUNCTION__
.':'.__LINE__
);
4744 if ($rp->radepth
< $hascap[$cap]->radepth
) {
4745 if ($hascap[$cap]->perm
!==0) {
4746 // Wider RA loses to local RAs...
4749 // "Higher RA resolves conflict" case,
4750 // local RAs had cancelled eachother
4751 $hascap[$cap] = $rp;
4755 // Same ralevel - locality of RC wins
4756 if ($rp->rcdepth
> $hascap[$cap]->rcdepth
) {
4757 $hascap[$cap] = $rp;
4760 if ($rp->rcdepth
> $hascap[$cap]->rcdepth
) {
4763 // We match depth - add them
4764 $hascap[$cap]->perm +
= $rp->perm
;
4767 if ($hascap[$capability]->perm
> 0
4768 ||
($doanything && isset($hascap['moodle/site:candoanything'])
4769 && $hascap['moodle/site:candoanything']->perm
> 0)) {
4776 * Will re-sort a $users results array (from get_users_by_capability(), usually)
4777 * based on a sorting policy. This is to support the odd practice of
4778 * sorting teachers by 'authority', where authority was "lowest id of the role
4781 * Will execute 1 database query. Only suitable for small numbers of users, as it
4782 * uses an u.id IN() clause.
4784 * Notes about the sorting criteria.
4786 * As a default, we cannot rely on role.sortorder because then
4787 * admins/coursecreators will always win. That is why the sane
4788 * rule "is locality matters most", with sortorder as 2nd
4791 * If you want role.sortorder, use the 'sortorder' policy, and
4792 * name explicitly what roles you want to cover. It's probably
4793 * a good idea to see what roles have the capabilities you want
4794 * (array_diff() them against roiles that have 'can-do-anything'
4795 * to weed out admin-ish roles. Or fetch a list of roles from
4796 * variables like $CFG->coursemanagers .
4798 * @param array users Users' array, keyed on userid
4799 * @param object context
4800 * @param array roles - ids of the roles to include, optional
4801 * @param string policy - defaults to locality, more about
4802 * @return array - sorted copy of the array
4804 function sort_by_roleassignment_authority($users, $context, $roles=array(), $sortpolicy='locality') {
4807 $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
4808 $contextwhere = ' ra.contextid IN ('.str_replace('/', ',',substr($context->path
, 1)).')';
4809 if (empty($roles)) {
4812 $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
4815 $sql = "SELECT ra.userid
4816 FROM {$CFG->prefix}role_assignments ra
4817 JOIN {$CFG->prefix}role r
4819 JOIN {$CFG->prefix}context ctx
4820 ON ra.contextid=ctx.id
4827 // Default 'locality' policy -- read PHPDoc notes
4828 // about sort policies...
4829 $orderby = 'ORDER BY
4830 ctx.depth DESC, /* locality wins */
4831 r.sortorder ASC, /* rolesorting 2nd criteria */
4832 ra.id /* role assignment order tie-breaker */';
4833 if ($sortpolicy === 'sortorder') {
4834 $orderby = 'ORDER BY
4835 r.sortorder ASC, /* rolesorting 2nd criteria */
4836 ra.id /* role assignment order tie-breaker */';
4839 $sortedids = get_fieldset_sql($sql . $orderby);
4840 $sortedusers = array();
4843 foreach ($sortedids as $id) {
4845 if (isset($seen[$id])) {
4851 $sortedusers[$id] = $users[$id];
4853 return $sortedusers;
4857 * gets all the users assigned this role in this context or higher
4858 * @param int roleid (can also be an array of ints!)
4859 * @param int contextid
4860 * @param bool parent if true, get list of users assigned in higher context too
4861 * @param string fields - fields from user (u.) , role assignment (ra) or role (r.)
4862 * @param string sort - sort from user (u.) , role assignment (ra) or role (r.)
4863 * @param bool gethidden - whether to fetch hidden enrolments too
4866 function get_role_users($roleid, $context, $parent=false, $fields='', $sort='u.lastname ASC', $gethidden=true, $group='', $limitfrom='', $limitnum='') {
4869 if (empty($fields)) {
4870 $fields = 'u.id, u.confirmed, u.username, u.firstname, u.lastname, '.
4871 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.city, '.
4872 'u.country, u.picture, u.idnumber, u.department, u.institution, '.
4873 'u.emailstop, u.lang, u.timezone, r.name as rolename';
4876 // whether this assignment is hidden
4877 $hiddensql = $gethidden ?
'': ' AND ra.hidden = 0 ';
4879 $parentcontexts = '';
4881 $parentcontexts = substr($context->path
, 1); // kill leading slash
4882 $parentcontexts = str_replace('/', ',', $parentcontexts);
4883 if ($parentcontexts !== '') {
4884 $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
4888 if (is_array($roleid)) {
4889 $roleselect = ' AND ra.roleid IN (' . implode(',',$roleid) .')';
4890 } elseif (!empty($roleid)) { // should not test for int, because it can come in as a string
4891 $roleselect = "AND ra.roleid = $roleid";
4897 $groupjoin = "JOIN {$CFG->prefix}groups_members gm
4898 ON gm.userid = u.id";
4899 $groupselect = " AND gm.groupid = $group ";
4905 $SQL = "SELECT $fields, ra.roleid
4906 FROM {$CFG->prefix}role_assignments ra
4907 JOIN {$CFG->prefix}user u
4909 JOIN {$CFG->prefix}role r
4912 WHERE (ra.contextid = $context->id $parentcontexts)
4917 "; // join now so that we can just use fullname() later
4919 return get_records_sql($SQL, $limitfrom, $limitnum);
4923 * Counts all the users assigned this role in this context or higher
4925 * @param int contextid
4926 * @param bool parent if true, get list of users assigned in higher context too
4929 function count_role_users($roleid, $context, $parent=false) {
4933 if ($contexts = get_parent_contexts($context)) {
4934 $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
4936 $parentcontexts = '';
4939 $parentcontexts = '';
4942 $SQL = "SELECT count(u.id)
4943 FROM {$CFG->prefix}role_assignments r
4944 JOIN {$CFG->prefix}user u
4946 WHERE (r.contextid = $context->id $parentcontexts)
4947 AND r.roleid = $roleid
4950 return count_records_sql($SQL);
4954 * This function gets the list of courses that this user has a particular capability in.
4955 * It is still not very efficient.
4956 * @param string $capability Capability in question
4957 * @param int $userid User ID or null for current user
4958 * @param bool $doanything True if 'doanything' is permitted (default)
4959 * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
4960 * otherwise use a comma-separated list of the fields you require, not including id
4961 * @param string $orderby If set, use a comma-separated list of fields from course
4962 * table with sql modifiers (DESC) if needed
4963 * @return array Array of courses, may have zero entries. Or false if query failed.
4965 function get_user_capability_course($capability, $userid=NULL,$doanything=true,$fieldsexceptid='',$orderby='') {
4966 // Convert fields list and ordering
4968 if($fieldsexceptid) {
4969 $fields=explode(',',$fieldsexceptid);
4970 foreach($fields as $field) {
4971 $fieldlist.=',c.'.$field;
4975 $fields=explode(',',$orderby);
4977 foreach($fields as $field) {
4981 $orderby.='c.'.$field;
4983 $orderby='ORDER BY '.$orderby;
4986 // Obtain a list of everything relevant about all courses including context.
4987 // Note the result can be used directly as a context (we are going to), the course
4988 // fields are just appended.
4990 $rs=get_recordset_sql("
4992 x.*,c.id AS courseid$fieldlist
4994 {$CFG->prefix}course c
4995 INNER JOIN {$CFG->prefix}context x ON c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE
."
5002 // Check capability for each course in turn
5004 while($coursecontext=rs_fetch_next_record($rs)) {
5005 if(has_capability($capability,$coursecontext,$userid,$doanything)) {
5006 // We've got the capability. Make the record look like a course record
5008 $coursecontext->id
=$coursecontext->courseid
;
5009 unset($coursecontext->courseid
);
5010 unset($coursecontext->contextlevel
);
5011 unset($coursecontext->instanceid
);
5012 $courses[]=$coursecontext;
5018 /** This function finds the roles assigned directly to this context only
5019 * i.e. no parents role
5020 * @param object $context
5023 function get_roles_on_exact_context($context) {
5027 return get_records_sql("SELECT r.*
5028 FROM {$CFG->prefix}role_assignments ra,
5029 {$CFG->prefix}role r
5030 WHERE ra.roleid = r.id
5031 AND ra.contextid = $context->id");
5036 * Switches the current user to another role for the current session and only
5037 * in the given context.
5039 * The caller *must* check
5040 * - that this op is allowed
5041 * - that the requested role can be assigned in this ctx
5042 * (hint, use get_assignable_roles_for_switchrole())
5043 * - that the requested role is NOT $CFG->defaultuserroleid
5045 * To "unswitch" pass 0 as the roleid.
5047 * This function *will* modify $USER->access - beware
5049 * @param integer $roleid
5050 * @param object $context
5053 function role_switch($roleid, $context) {
5059 // - Add the ghost RA to $USER->access
5060 // as $USER->access['rsw'][$path] = $roleid
5062 // - Make sure $USER->access['rdef'] has the roledefs
5063 // it needs to honour the switcheroo
5065 // Roledefs will get loaded "deep" here - down to the last child
5066 // context. Note that
5068 // - When visiting subcontexts, our selective accessdata loading
5069 // will still work fine - though those ra/rdefs will be ignored
5070 // appropriately while the switch is in place
5072 // - If a switcheroo happens at a category with tons of courses
5073 // (that have many overrides for switched-to role), the session
5074 // will get... quite large. Sometimes you just can't win.
5076 // To un-switch just unset($USER->access['rsw'][$path])
5079 // Add the switch RA
5080 if (!isset($USER->access
['rsw'])) {
5081 $USER->access
['rsw'] = array();
5085 unset($USER->access
['rsw'][$context->path
]);
5086 if (empty($USER->access
['rsw'])) {
5087 unset($USER->access
['rsw']);
5092 $USER->access
['rsw'][$context->path
]=$roleid;
5095 $USER->access
= get_role_access_bycontext($roleid, $context,
5098 /* DO WE NEED THIS AT ALL???
5099 // Add some permissions we are really going
5100 // to always need, even if the role doesn't have them!
5102 $USER->capabilities[$context->id]['moodle/course:view'] = CAP_ALLOW;
5109 // get any role that has an override on exact context
5110 function get_roles_with_override_on_context($context) {
5114 return get_records_sql("SELECT r.*
5115 FROM {$CFG->prefix}role_capabilities rc,
5116 {$CFG->prefix}role r
5117 WHERE rc.roleid = r.id
5118 AND rc.contextid = $context->id");
5121 // get all capabilities for this role on this context (overrids)
5122 function get_capabilities_from_role_on_context($role, $context) {
5126 return get_records_sql("SELECT *
5127 FROM {$CFG->prefix}role_capabilities
5128 WHERE contextid = $context->id
5129 AND roleid = $role->id");
5132 // find out which roles has assignment on this context
5133 function get_roles_with_assignment_on_context($context) {
5137 return get_records_sql("SELECT r.*
5138 FROM {$CFG->prefix}role_assignments ra,
5139 {$CFG->prefix}role r
5140 WHERE ra.roleid = r.id
5141 AND ra.contextid = $context->id");
5147 * Find all user assignemnt of users for this role, on this context
5149 function get_users_from_role_on_context($role, $context) {
5153 return get_records_sql("SELECT *
5154 FROM {$CFG->prefix}role_assignments
5155 WHERE contextid = $context->id
5156 AND roleid = $role->id");
5160 * Simple function returning a boolean true if roles exist, otherwise false
5162 function user_has_role_assignment($userid, $roleid, $contextid=0) {
5165 return record_exists('role_assignments', 'userid', $userid, 'roleid', $roleid, 'contextid', $contextid);
5167 return record_exists('role_assignments', 'userid', $userid, 'roleid', $roleid);
5172 * Get role name or alias if exists and format the text.
5173 * @param object $role role object
5174 * @param object $coursecontext
5175 * @return $string name of role in course context
5177 function role_get_name($role, $coursecontext) {
5178 if ($r = get_record('role_names','roleid', $role->id
,'contextid', $coursecontext->id
)) {
5179 return strip_tags(format_string($r->name
));
5181 return strip_tags(format_string($role->name
));
5186 * Prepare list of roles for display, apply aliases and format text
5187 * @param array $roleoptions array roleid=>rolename
5188 * @param object $context
5189 * @return array of role names
5191 function role_fix_names($roleoptions, $context, $rolenamedisplay=ROLENAME_ALIAS
) {
5192 if ($rolenamedisplay != ROLENAME_ORIGINAL
&& !empty($context->id
)) {
5193 if ($context->contextlevel
== CONTEXT_MODULE ||
$context->contextlevel
== CONTEXT_BLOCK
) { // find the parent course context
5194 if ($parentcontextid = array_shift(get_parent_contexts($context))) {
5195 $context = get_context_instance_by_id($parentcontextid);
5198 if ($aliasnames = get_records('role_names', 'contextid', $context->id
)) {
5199 if ($rolenamedisplay == ROLENAME_ALIAS
) {
5200 foreach ($aliasnames as $alias) {
5201 if (isset($roleoptions[$alias->roleid
])) {
5202 $roleoptions[$alias->roleid
] = format_string($alias->name
);
5205 } else if ($rolenamedisplay == ROLENAME_BOTH
) {
5206 foreach ($aliasnames as $alias) {
5207 if (isset($roleoptions[$alias->roleid
])) {
5208 $roleoptions[$alias->roleid
] = format_string($alias->name
).' ('.format_string($roleoptions[$alias->roleid
]).')';
5214 foreach ($roleoptions as $rid => $name) {
5215 $roleoptions[$rid] = strip_tags($name);
5217 return $roleoptions;
5221 * This function helps admin/roles/manage.php etc to detect if a new line should be printed
5222 * when we read in a new capability
5223 * most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
5224 * but when we are in grade, all reports/import/export capabilites should be together
5225 * @param string a - component string a
5226 * @param string b - component string b
5227 * @return bool - whether 2 component are in different "sections"
5229 function component_level_changed($cap, $comp, $contextlevel) {
5231 if ($cap->component
== 'enrol/authorize' && $comp =='enrol/authorize') {
5235 if (strstr($cap->component
, '/') && strstr($comp, '/')) {
5236 $compsa = explode('/', $cap->component
);
5237 $compsb = explode('/', $comp);
5241 // we are in gradebook, still
5242 if (($compsa[0] == 'gradeexport' ||
$compsa[0] == 'gradeimport' ||
$compsa[0] == 'gradereport') &&
5243 ($compsb[0] == 'gradeexport' ||
$compsb[0] == 'gradeimport' ||
$compsb[0] == 'gradereport')) {
5248 return ($cap->component
!= $comp ||
$cap->contextlevel
!= $contextlevel);
5252 * Populate context.path and context.depth where missing.
5253 * @param bool $force force a complete rebuild of the path and depth fields.
5254 * @param bool $feedback display feedback (during upgrade usually)
5257 function build_context_path($force=false, $feedback=false) {
5259 require_once($CFG->libdir
.'/ddllib.php');
5262 $sitectx = get_system_context(!$force);
5263 $base = '/'.$sitectx->id
;
5266 $sitecoursectx = get_record('context',
5267 'contextlevel', CONTEXT_COURSE
,
5268 'instanceid', SITEID
);
5269 if ($force ||
$sitecoursectx->path
!== "$base/{$sitecoursectx->id}") {
5270 set_field('context', 'path', "$base/{$sitecoursectx->id}",
5271 'id', $sitecoursectx->id
);
5272 set_field('context', 'depth', 2,
5273 'id', $sitecoursectx->id
);
5274 $sitecoursectx = get_record('context',
5275 'contextlevel', CONTEXT_COURSE
,
5276 'instanceid', SITEID
);
5279 $ctxemptyclause = " AND (ctx.path IS NULL
5281 $emptyclause = " AND ({$CFG->prefix}context.path IS NULL
5282 OR {$CFG->prefix}context.depth=0) ";
5284 $ctxemptyclause = $emptyclause = '';
5288 * - mysql does not allow to use FROM in UPDATE statements
5289 * - using two tables after UPDATE works in mysql, but might give unexpected
5290 * results in pg 8 (depends on configuration)
5291 * - using table alias in UPDATE does not work in pg < 8.2
5293 if ($CFG->dbfamily
== 'mysql') {
5294 $updatesql = "UPDATE {$CFG->prefix}context ct, {$CFG->prefix}context_temp temp
5295 SET ct.path = temp.path,
5296 ct.depth = temp.depth
5297 WHERE ct.id = temp.id";
5298 } else if ($CFG->dbfamily
== 'oracle') {
5299 $updatesql = "UPDATE {$CFG->prefix}context ct
5300 SET (ct.path, ct.depth) =
5301 (SELECT temp.path, temp.depth
5302 FROM {$CFG->prefix}context_temp temp
5303 WHERE temp.id=ct.id)
5304 WHERE EXISTS (SELECT 'x'
5305 FROM {$CFG->prefix}context_temp temp
5306 WHERE temp.id = ct.id)";
5308 $updatesql = "UPDATE {$CFG->prefix}context
5309 SET path = temp.path,
5311 FROM {$CFG->prefix}context_temp temp
5312 WHERE temp.id={$CFG->prefix}context.id";
5315 $udelsql = "TRUNCATE TABLE {$CFG->prefix}context_temp";
5317 // Top level categories
5318 $sql = "UPDATE {$CFG->prefix}context
5319 SET depth=2, path=" . sql_concat("'$base/'", 'id') . "
5320 WHERE contextlevel=".CONTEXT_COURSECAT
."
5321 AND EXISTS (SELECT 'x'
5322 FROM {$CFG->prefix}course_categories cc
5323 WHERE cc.id = {$CFG->prefix}context.instanceid
5327 execute_sql($sql, $feedback);
5329 execute_sql($udelsql, $feedback);
5331 // Deeper categories - one query per depthlevel
5332 $maxdepth = get_field_sql("SELECT MAX(depth)
5333 FROM {$CFG->prefix}course_categories");
5334 for ($n=2;$n<=$maxdepth;$n++
) {
5335 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5336 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", $n+1
5337 FROM {$CFG->prefix}context ctx
5338 JOIN {$CFG->prefix}course_categories c ON ctx.instanceid=c.id
5339 JOIN {$CFG->prefix}context pctx ON c.parent=pctx.instanceid
5340 WHERE ctx.contextlevel=".CONTEXT_COURSECAT
."
5341 AND pctx.contextlevel=".CONTEXT_COURSECAT
."
5343 AND NOT EXISTS (SELECT 'x'
5344 FROM {$CFG->prefix}context_temp temp
5345 WHERE temp.id = ctx.id)
5347 execute_sql($sql, $feedback);
5349 // this is needed after every loop
5351 execute_sql($updatesql, $feedback);
5352 execute_sql($udelsql, $feedback);
5355 // Courses -- except sitecourse
5356 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5357 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5358 FROM {$CFG->prefix}context ctx
5359 JOIN {$CFG->prefix}course c ON ctx.instanceid=c.id
5360 JOIN {$CFG->prefix}context pctx ON c.category=pctx.instanceid
5361 WHERE ctx.contextlevel=".CONTEXT_COURSE
."
5362 AND c.id!=".SITEID
."
5363 AND pctx.contextlevel=".CONTEXT_COURSECAT
."
5364 AND NOT EXISTS (SELECT 'x'
5365 FROM {$CFG->prefix}context_temp temp
5366 WHERE temp.id = ctx.id)
5368 execute_sql($sql, $feedback);
5370 execute_sql($updatesql, $feedback);
5371 execute_sql($udelsql, $feedback);
5374 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5375 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5376 FROM {$CFG->prefix}context ctx
5377 JOIN {$CFG->prefix}course_modules cm ON ctx.instanceid=cm.id
5378 JOIN {$CFG->prefix}context pctx ON cm.course=pctx.instanceid
5379 WHERE ctx.contextlevel=".CONTEXT_MODULE
."
5380 AND pctx.contextlevel=".CONTEXT_COURSE
."
5381 AND NOT EXISTS (SELECT 'x'
5382 FROM {$CFG->prefix}context_temp temp
5383 WHERE temp.id = ctx.id)
5385 execute_sql($sql, $feedback);
5387 execute_sql($updatesql, $feedback);
5388 execute_sql($udelsql, $feedback);
5390 // Blocks - non-pinned course-view only
5391 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5392 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5393 FROM {$CFG->prefix}context ctx
5394 JOIN {$CFG->prefix}block_instance bi ON ctx.instanceid = bi.id
5395 JOIN {$CFG->prefix}context pctx ON bi.pageid=pctx.instanceid
5396 WHERE ctx.contextlevel=".CONTEXT_BLOCK
."
5397 AND pctx.contextlevel=".CONTEXT_COURSE
."
5398 AND bi.pagetype='course-view'
5399 AND NOT EXISTS (SELECT 'x'
5400 FROM {$CFG->prefix}context_temp temp
5401 WHERE temp.id = ctx.id)
5403 execute_sql($sql, $feedback);
5405 execute_sql($updatesql, $feedback);
5406 execute_sql($udelsql, $feedback);
5409 $sql = "UPDATE {$CFG->prefix}context
5410 SET depth=2, path=".sql_concat("'$base/'", 'id')."
5411 WHERE contextlevel=".CONTEXT_BLOCK
."
5412 AND EXISTS (SELECT 'x'
5413 FROM {$CFG->prefix}block_instance bi
5414 WHERE bi.id = {$CFG->prefix}context.instanceid
5415 AND bi.pagetype!='course-view')
5417 execute_sql($sql, $feedback);
5420 $sql = "UPDATE {$CFG->prefix}context
5421 SET depth=2, path=".sql_concat("'$base/'", 'id')."
5422 WHERE contextlevel=".CONTEXT_USER
."
5423 AND EXISTS (SELECT 'x'
5424 FROM {$CFG->prefix}user u
5425 WHERE u.id = {$CFG->prefix}context.instanceid)
5427 execute_sql($sql, $feedback);
5431 //TODO: fix group contexts
5433 // reset static course cache - it might have incorrect cached data
5434 global $context_cache, $context_cache_id;
5435 $context_cache = array();
5436 $context_cache_id = array();
5441 * Update the path field of the context and
5442 * all the dependent subcontexts that follow
5445 * The most important thing here is to be as
5446 * DB efficient as possible. This op can have a
5447 * massive impact in the DB.
5449 * @param obj current context obj
5450 * @param obj newparent new parent obj
5453 function context_moved($context, $newparent) {
5456 $frompath = $context->path
;
5457 $newpath = $newparent->path
. '/' . $context->id
;
5460 if (($newparent->depth +
1) != $context->depth
) {
5461 $setdepth = ", depth= depth + ({$newparent->depth} - {$context->depth}) + 1";
5463 $sql = "UPDATE {$CFG->prefix}context
5466 WHERE path='$frompath'";
5467 execute_sql($sql,false);
5469 $len = strlen($frompath);
5470 /// MDL-16655 - Substring MSSQL function *requires* 3rd parameter
5471 $substr3rdparam = '';
5472 if ($CFG->dbfamily
== 'mssql') {
5473 $substr3rdparam = ', len(path)';
5475 $sql = "UPDATE {$CFG->prefix}context
5476 SET path = ".sql_concat("'$newpath'", sql_substr() .'(path, '.$len.' +1'.$substr3rdparam.')')."
5478 WHERE path LIKE '{$frompath}/%'";
5479 execute_sql($sql,false);
5481 mark_context_dirty($frompath);
5482 mark_context_dirty($newpath);
5487 * Turn the ctx* fields in an objectlike record
5488 * into a context subobject. This allows
5489 * us to SELECT from major tables JOINing with
5490 * context at no cost, saving a ton of context
5493 function make_context_subobj($rec) {
5494 $ctx = new StdClass
;
5495 $ctx->id
= $rec->ctxid
; unset($rec->ctxid
);
5496 $ctx->path
= $rec->ctxpath
; unset($rec->ctxpath
);
5497 $ctx->depth
= $rec->ctxdepth
; unset($rec->ctxdepth
);
5498 $ctx->contextlevel
= $rec->ctxlevel
; unset($rec->ctxlevel
);
5499 $ctx->instanceid
= $rec->id
;
5501 $rec->context
= $ctx;
5506 * Fetch recent dirty contexts to know cheaply whether our $USER->access
5507 * is stale and needs to be reloaded.
5511 * @return array of dirty contexts
5513 function get_dirty_contexts($time) {
5514 return get_cache_flags('accesslib/dirtycontexts', $time-2);
5518 * Mark a context as dirty (with timestamp)
5519 * so as to force reloading of the context.
5520 * @param string $path context path
5522 function mark_context_dirty($path) {
5523 global $CFG, $DIRTYCONTEXTS;
5524 // only if it is a non-empty string
5525 if (is_string($path) && $path !== '') {
5526 set_cache_flag('accesslib/dirtycontexts', $path, 1, time()+
$CFG->sessiontimeout
);
5527 if (isset($DIRTYCONTEXTS)) {
5528 $DIRTYCONTEXTS[$path] = 1;
5534 * Will walk the contextpath to answer whether
5535 * the contextpath is dirty
5537 * @param array $contexts array of strings
5538 * @param obj/array dirty contexts from get_dirty_contexts()
5541 function is_contextpath_dirty($pathcontexts, $dirty) {
5543 foreach ($pathcontexts as $ctx) {
5544 $path = $path.'/'.$ctx;
5545 if (isset($dirty[$path])) {
5554 * switch role order (used in admin/roles/manage.php)
5556 * @param int $first id of role to move down
5557 * @param int $second id of role to move up
5559 * @return bool success or failure
5561 function switch_roles($first, $second) {
5563 //first find temorary sortorder number
5564 $tempsort = count_records('role') +
3;
5565 while (get_record('role','sortorder', $tempsort)) {
5570 $r1->id
= $first->id
;
5571 $r1->sortorder
= $tempsort;
5573 $r2->id
= $second->id
;
5574 $r2->sortorder
= $first->sortorder
;
5576 if (!update_record('role', $r1)) {
5577 debugging("Can not update role with ID $r1->id!");
5581 if (!update_record('role', $r2)) {
5582 debugging("Can not update role with ID $r2->id!");
5586 $r1->sortorder
= $second->sortorder
;
5587 if (!update_record('role', $r1)) {
5588 debugging("Can not update role with ID $r1->id!");
5596 * duplicates all the base definitions of a role
5598 * @param object $sourcerole role to copy from
5599 * @param int $targetrole id of role to copy to
5603 function role_cap_duplicate($sourcerole, $targetrole) {
5605 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
5606 $caps = get_records_sql("SELECT * FROM {$CFG->prefix}role_capabilities
5607 WHERE roleid = $sourcerole->id
5608 AND contextid = $systemcontext->id");
5609 // adding capabilities
5610 foreach ($caps as $cap) {
5612 $cap->roleid
= $targetrole;
5613 insert_record('role_capabilities', $cap);