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 * - has_any_capability()
41 * - has_all_capabilities()
42 * - require_capability()
43 * - require_login() (from moodlelib)
45 * What courses has this user access to?
46 * - get_user_courses_bycap()
48 * What users can do X in this context?
49 * - get_users_by_capability()
52 * - enrol_into_course()
53 * - role_assign()/role_unassign()
57 * - load_all_capabilities()
58 * - reload_all_capabilities()
60 * - has_capability_in_accessdata()
62 * - get_user_access_sitewide()
64 * - get_role_access_bycontext()
69 * - "ctx" means context
74 * Access control data is held in the "accessdata" array
75 * which - for the logged-in user, will be in $USER->access
77 * For other users can be generated and passed around (but see
78 * the $ACCESS global).
80 * $accessdata is a multidimensional array, holding
81 * role assignments (RAs), role-capabilities-perm sets
82 * (role defs) and a list of courses we have loaded
85 * Things are keyed on "contextpaths" (the path field of
86 * the context table) for fast walking up/down the tree.
88 * $accessdata[ra][$contextpath]= array($roleid)
89 * [$contextpath]= array($roleid)
90 * [$contextpath]= array($roleid)
92 * Role definitions are stored like this
93 * (no cap merge is done - so it's compact)
95 * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
96 * [mod/forum:editallpost] = -1
97 * [mod/forum:startdiscussion] = -1000
99 * See how has_capability_in_accessdata() walks up/down the tree.
101 * Normally - specially for the logged-in user, we only load
102 * rdef and ra down to the course level, but not below. This
103 * keeps accessdata small and compact. Below-the-course ra/rdef
104 * are loaded as needed. We keep track of which courses we
105 * have loaded ra/rdef in
107 * $accessdata[loaded] = array($contextpath, $contextpath)
112 * For the logged-in user, accessdata is long-lived.
114 * On each pageload we load $DIRTYPATHS which lists
115 * context paths affected by changes. Any check at-or-below
116 * a dirty context will trigger a transparent reload of accessdata.
118 * Changes at the sytem level will force the reload for everyone.
122 * The default role assignment is not in the DB, so we
123 * add it manually to accessdata.
125 * This means that functions that work directly off the
126 * DB need to ensure that the default role caps
127 * are dealt with appropriately.
131 require_once $CFG->dirroot
.'/lib/blocklib.php';
133 // permission definitions
134 define('CAP_INHERIT', 0);
135 define('CAP_ALLOW', 1);
136 define('CAP_PREVENT', -1);
137 define('CAP_PROHIBIT', -1000);
139 // context definitions
140 define('CONTEXT_SYSTEM', 10);
141 define('CONTEXT_USER', 30);
142 define('CONTEXT_COURSECAT', 40);
143 define('CONTEXT_COURSE', 50);
144 define('CONTEXT_GROUP', 60);
145 define('CONTEXT_MODULE', 70);
146 define('CONTEXT_BLOCK', 80);
148 // capability risks - see http://docs.moodle.org/en/Development:Hardening_new_Roles_system
149 define('RISK_MANAGETRUST', 0x0001);
150 define('RISK_CONFIG', 0x0002);
151 define('RISK_XSS', 0x0004);
152 define('RISK_PERSONAL', 0x0008);
153 define('RISK_SPAM', 0x0010);
154 define('RISK_DATALOSS', 0x0020);
157 define('ROLENAME_ORIGINAL', 0);// the name as defined in the role definition
158 define('ROLENAME_ALIAS', 1); // the name as defined by a role alias
159 define('ROLENAME_BOTH', 2); // Both, like this: Role alias (Original)
161 require_once($CFG->dirroot
.'/group/lib.php');
163 $context_cache = array(); // Cache of all used context objects for performance (by level and instance)
164 $context_cache_id = array(); // Index to above cache by id
166 $DIRTYCONTEXTS = null; // dirty contexts cache
167 $ACCESS = array(); // cache of caps for cron user switching and has_capability for other users (==not $USER)
168 $RDEFS = array(); // role definitions cache - helps a lot with mem usage in cron
170 function get_role_context_caps($roleid, $context) {
171 //this is really slow!!!! - do not use above course context level!
173 $result[$context->id
] = array();
175 // first emulate the parent context capabilities merging into context
176 $searchcontexts = array_reverse(get_parent_contexts($context));
177 array_push($searchcontexts, $context->id
);
178 foreach ($searchcontexts as $cid) {
179 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
180 foreach ($capabilities as $cap) {
181 if (!array_key_exists($cap->capability
, $result[$context->id
])) {
182 $result[$context->id
][$cap->capability
] = 0;
184 $result[$context->id
][$cap->capability
] +
= $cap->permission
;
189 // now go through the contexts bellow given context
190 $searchcontexts = array_keys(get_child_contexts($context));
191 foreach ($searchcontexts as $cid) {
192 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
193 foreach ($capabilities as $cap) {
194 if (!array_key_exists($cap->contextid
, $result)) {
195 $result[$cap->contextid
] = array();
197 $result[$cap->contextid
][$cap->capability
] = $cap->permission
;
206 * Gets the accessdata for role "sitewide"
207 * (system down to course)
211 function get_role_access($roleid, $accessdata=NULL) {
215 /* Get it in 1 cheap DB query...
216 * - relevant role caps at the root and down
217 * to the course level - but not below
219 if (is_null($accessdata)) {
220 $accessdata = array(); // named list
221 $accessdata['ra'] = array();
222 $accessdata['rdef'] = array();
223 $accessdata['loaded'] = array();
227 // Overrides for the role IN ANY CONTEXTS
228 // down to COURSE - not below -
230 $sql = "SELECT ctx.path,
231 rc.capability, rc.permission
232 FROM {$CFG->prefix}context ctx
233 JOIN {$CFG->prefix}role_capabilities rc
234 ON rc.contextid=ctx.id
235 WHERE rc.roleid = {$roleid}
236 AND ctx.contextlevel <= ".CONTEXT_COURSE
."
237 ORDER BY ctx.depth, ctx.path";
239 // we need extra caching in cron only
240 if (defined('FULLME') and FULLME
=== 'cron') {
241 static $cron_cache = array();
243 if (!isset($cron_cache[$roleid])) {
244 $cron_cache[$roleid] = array();
245 if ($rs = get_recordset_sql($sql)) {
246 while ($rd = rs_fetch_next_record($rs)) {
247 $cron_cache[$roleid][] = $rd;
253 foreach ($cron_cache[$roleid] as $rd) {
254 $k = "{$rd->path}:{$roleid}";
255 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
259 if ($rs = get_recordset_sql($sql)) {
260 while ($rd = rs_fetch_next_record($rs)) {
261 $k = "{$rd->path}:{$roleid}";
262 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
273 * Gets the accessdata for role "sitewide"
274 * (system down to course)
278 function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
282 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
283 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
286 // Overrides for the role in any contexts related to the course
288 $sql = "SELECT ctx.path,
289 rc.capability, rc.permission
290 FROM {$CFG->prefix}context ctx
291 JOIN {$CFG->prefix}role_capabilities rc
292 ON rc.contextid=ctx.id
293 WHERE rc.roleid = {$roleid}
294 AND (ctx.id = ".SYSCONTEXTID
." OR ctx.path LIKE '$base/%')
295 AND ctx.contextlevel <= ".CONTEXT_COURSE
."
296 ORDER BY ctx.depth, ctx.path";
298 if ($rs = get_recordset_sql($sql)) {
299 while ($rd = rs_fetch_next_record($rs)) {
300 $k = "{$rd->path}:{$roleid}";
301 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
312 * Get the default guest role
313 * @return object role
315 function get_guest_role() {
318 if (empty($CFG->guestroleid
)) {
319 if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW
)) {
320 $guestrole = array_shift($roles); // Pick the first one
321 set_config('guestroleid', $guestrole->id
);
324 debugging('Can not find any guest role!');
328 if ($guestrole = get_record('role','id', $CFG->guestroleid
)) {
331 //somebody is messing with guest roles, remove incorrect setting and try to find a new one
332 set_config('guestroleid', '');
333 return get_guest_role();
339 * This function returns whether the current user has the capability of performing a function
340 * For example, we can do has_capability('mod/forum:replypost',$context) in forum
341 * @param string $capability - name of the capability (or debugcache or clearcache)
342 * @param object $context - a context object (record from context table)
343 * @param integer $userid - a userid number, empty if current $USER
344 * @param bool $doanything - if false, ignore do anything
347 function has_capability($capability, $context, $userid=NULL, $doanything=true) {
348 global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS;
350 // the original $CONTEXT here was hiding serious errors
351 // for security reasons do not reuse previous context
352 if (empty($context)) {
353 debugging('Incorrect context specified');
357 /// Some sanity checks
358 if (debugging('',DEBUG_DEVELOPER
)) {
359 if (!is_valid_capability($capability)) {
360 debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
362 if (!is_bool($doanything)) {
363 debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.');
367 if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid
371 if (is_null($context->path
) or $context->depth
== 0) {
372 //this should not happen
373 $contexts = array(SYSCONTEXTID
, $context->id
);
374 $context->path
= '/'.SYSCONTEXTID
.'/'.$context->id
;
375 debugging('Context id '.$context->id
.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER
);
378 $contexts = explode('/', $context->path
);
379 array_shift($contexts);
382 if (defined('FULLME') && FULLME
=== 'cron' && !isset($USER->access
)) {
383 // In cron, some modules setup a 'fake' $USER,
384 // ensure we load the appropriate accessdata.
385 if (isset($ACCESS[$userid])) {
386 $DIRTYCONTEXTS = NULL; //load fresh dirty contexts
388 load_user_accessdata($userid);
389 $DIRTYCONTEXTS = array();
391 $USER->access
= $ACCESS[$userid];
393 } else if ($USER->id
== $userid && !isset($USER->access
)) {
394 // caps not loaded yet - better to load them to keep BC with 1.8
395 // not-logged-in user or $USER object set up manually first time here
396 load_all_capabilities();
397 $ACCESS = array(); // reset the cache for other users too, the dirty contexts are empty now
401 // Load dirty contexts list if needed
402 if (!isset($DIRTYCONTEXTS)) {
403 if (isset($USER->access
['time'])) {
404 $DIRTYCONTEXTS = get_dirty_contexts($USER->access
['time']);
407 $DIRTYCONTEXTS = array();
411 // Careful check for staleness...
412 if (count($DIRTYCONTEXTS) !== 0 and is_contextpath_dirty($contexts, $DIRTYCONTEXTS)) {
413 // reload all capabilities - preserving loginas, roleswitches, etc
414 // and then cleanup any marks of dirtyness... at least from our short
419 if (defined('FULLME') && FULLME
=== 'cron') {
420 load_user_accessdata($userid);
421 $USER->access
= $ACCESS[$userid];
422 $DIRTYCONTEXTS = array();
425 reload_all_capabilities();
429 // divulge how many times we are called
430 //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
432 if ($USER->id
== $userid) { // we must accept strings and integers in $userid
434 // For the logged in user, we have $USER->access
435 // which will have all RAs and caps preloaded for
436 // course and above contexts.
438 // Contexts below courses && contexts that do not
439 // hang from courses are loaded into $USER->access
440 // on demand, and listed in $USER->access[loaded]
442 if ($context->contextlevel
<= CONTEXT_COURSE
) {
443 // Course and above are always preloaded
444 return has_capability_in_accessdata($capability, $context, $USER->access
, $doanything);
446 // Load accessdata for below-the-course contexts
447 if (!path_inaccessdata($context->path
,$USER->access
)) {
448 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
449 // $bt = debug_backtrace();
450 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
451 load_subcontext($USER->id
, $context, $USER->access
);
453 return has_capability_in_accessdata($capability, $context, $USER->access
, $doanything);
456 if (!isset($ACCESS[$userid])) {
457 load_user_accessdata($userid);
459 if ($context->contextlevel
<= CONTEXT_COURSE
) {
460 // Course and above are always preloaded
461 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
463 // Load accessdata for below-the-course contexts as needed
464 if (!path_inaccessdata($context->path
, $ACCESS[$userid])) {
465 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
466 // $bt = debug_backtrace();
467 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
468 load_subcontext($userid, $context, $ACCESS[$userid]);
470 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
474 * This function returns whether the current user has any of the capabilities in the
475 * $capabilities array. This is a simple wrapper around has_capability for convinience.
477 * There are probably tricks that could be done to improve the performance here, for example,
478 * check the capabilities that are already cached first.
480 * @param array $capabilities - an array of capability names.
481 * @param object $context - a context object (record from context table)
482 * @param integer $userid - a userid number, empty if current $USER
483 * @param bool $doanything - if false, ignore do anything
486 function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
487 foreach ($capabilities as $capability) {
488 if (has_capability($capability, $context, $userid, $doanything)) {
496 * This function returns whether the current user has all of the capabilities in the
497 * $capabilities array. This is a simple wrapper around has_capability for convinience.
499 * There are probably tricks that could be done to improve the performance here, for example,
500 * check the capabilities that are already cached first.
502 * @param array $capabilities - an array of capability names.
503 * @param object $context - a context object (record from context table)
504 * @param integer $userid - a userid number, empty if current $USER
505 * @param bool $doanything - if false, ignore do anything
508 function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) {
509 if (!is_array($capabilities)) {
510 debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
513 foreach ($capabilities as $capability) {
514 if (!has_capability($capability, $context, $userid, $doanything)) {
522 * Uses 1 DB query to answer whether a user is an admin at the sitelevel.
523 * It depends on DB schema >=1.7 but does not depend on the new datastructures
524 * in v1.9 (context.path, or $USER->access)
526 * Will return true if the userid has any of
527 * - moodle/site:config
528 * - moodle/legacy:admin
529 * - moodle/site:doanything
532 * @returns bool $isadmin
534 function is_siteadmin($userid) {
537 $sql = "SELECT SUM(rc.permission)
538 FROM " . $CFG->prefix
. "role_capabilities rc
539 JOIN " . $CFG->prefix
. "context ctx
540 ON ctx.id=rc.contextid
541 JOIN " . $CFG->prefix
. "role_assignments ra
542 ON ra.roleid=rc.roleid AND ra.contextid=ctx.id
543 WHERE ctx.contextlevel=10
544 AND ra.userid={$userid}
545 AND rc.capability IN ('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything')
546 GROUP BY rc.capability
547 HAVING SUM(rc.permission) > 0";
549 $isadmin = record_exists_sql($sql);
553 function get_course_from_path ($path) {
554 // assume that nothing is more than 1 course deep
555 if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
561 function path_inaccessdata($path, $accessdata) {
563 // assume that contexts hang from sys or from a course
564 // this will only work well with stuff that hangs from a course
565 if (in_array($path, $accessdata['loaded'], true)) {
566 // error_log("found it!");
569 $base = '/' . SYSCONTEXTID
;
570 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
572 if ($path === $base) {
575 if (in_array($path, $accessdata['loaded'], true)) {
583 * Walk the accessdata array and return true/false.
584 * Deals with prohibits, roleswitching, aggregating
587 * The main feature of here is being FAST and with no
592 * Switch Roles exits early
593 * -----------------------
594 * cap checks within a switchrole need to exit early
595 * in our bottom up processing so they don't "see" that
596 * there are real RAs that can do all sorts of things.
598 * Switch Role merges with default role
599 * ------------------------------------
600 * If you are a teacher in course X, you have at least
601 * teacher-in-X + defaultloggedinuser-sitewide. So in the
602 * course you'll have techer+defaultloggedinuser.
603 * We try to mimic that in switchrole.
605 * Local-most role definition and role-assignment wins
606 * ---------------------------------------------------
607 * So if the local context has said 'allow', it wins
608 * over a high-level context that says 'deny'.
609 * This is applied when walking rdefs, and RAs.
610 * Only at the same context the values are SUM()med.
612 * The exception is CAP_PROHIBIT.
614 * "Guest default role" exception
615 * ------------------------------
617 * See MDL-7513 and $ignoreguest below for details.
621 * IF we are being asked about moodle/legacy:guest
622 * OR moodle/course:view
623 * FOR a real, logged-in user
624 * AND we reached the top of the path in ra and rdef
625 * AND that role has moodle/legacy:guest === 1...
626 * THEN we act as if we hadn't seen it.
628 * Note that this function must be kept in synch with has_capability_in_accessdata.
632 * - Document how it works
633 * - Rewrite in ASM :-)
636 function has_capability_in_accessdata($capability, $context, $accessdata, $doanything) {
640 $path = $context->path
;
642 // build $contexts as a list of "paths" of the current
643 // contexts and parents with the order top-to-bottom
644 $contexts = array($path);
645 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
647 array_unshift($contexts, $path);
650 $ignoreguest = false;
651 if (isset($accessdata['dr'])
652 && ($capability == 'moodle/course:view'
653 ||
$capability == 'moodle/legacy:guest')) {
654 // At the base, ignore rdefs where moodle/legacy:guest
656 $ignoreguest = $accessdata['dr'];
659 // Coerce it to an int
660 $CAP_PROHIBIT = (int)CAP_PROHIBIT
;
662 $cc = count($contexts);
668 // role-switches loop
670 if (isset($accessdata['rsw'])) {
671 // check for isset() is fast
672 // empty() is slow...
673 if (empty($accessdata['rsw'])) {
674 unset($accessdata['rsw']); // keep things fast and unambiguous
677 // From the bottom up...
678 for ($n=$cc-1;$n>=0;$n--) {
679 $ctxp = $contexts[$n];
680 if (isset($accessdata['rsw'][$ctxp])) {
681 // Found a switchrole assignment
682 // check for that role _plus_ the default user role
683 $ras = array($accessdata['rsw'][$ctxp],$CFG->defaultuserroleid
);
684 for ($rn=0;$rn<2;$rn++
) {
685 $roleid = (int)$ras[$rn];
686 // Walk the path for capabilities
687 // from the bottom up...
688 for ($m=$cc-1;$m>=0;$m--) {
689 $capctxp = $contexts[$m];
690 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
691 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
693 // The most local permission (first to set) wins
694 // the only exception is CAP_PROHIBIT
697 } elseif ($perm === $CAP_PROHIBIT) {
704 // As we are dealing with a switchrole,
705 // we return _here_, do _not_ walk up
706 // the hierarchy any further
709 // didn't find it as an explicit cap,
710 // but maybe the user can doanything in this context...
711 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
724 // Main loop for normal RAs
725 // From the bottom up...
727 for ($n=$cc-1;$n>=0;$n--) {
728 $ctxp = $contexts[$n];
729 if (isset($accessdata['ra'][$ctxp])) {
730 // Found role assignments on this leaf
731 $ras = $accessdata['ra'][$ctxp];
736 for ($rn=0;$rn<$rc;$rn++
) {
737 $roleid = (int)$ras[$rn];
740 // Walk the path for capabilities
741 // from the bottom up...
742 for ($m=$cc-1;$m>=0;$m--) {
743 $capctxp = $contexts[$m];
744 // ignore some guest caps
745 // at base ra and rdef
746 if ($ignoreguest == $roleid
749 && isset($accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'])
750 && $accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'] > 0) {
753 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
754 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
755 // The most local permission (first to set) wins
756 // the only exception is CAP_PROHIBIT
757 if ($rolecan === 0) {
760 } elseif ($perm === $CAP_PROHIBIT) {
767 // Rules for RAs at the same context...
768 // - prohibits always wins
769 // - permissions at the same ctxlevel & capdepth are added together
770 // - deeper capdepth wins
771 if ($ctxcan === $CAP_PROHIBIT ||
$rolecan === $CAP_PROHIBIT) {
772 $ctxcan = $CAP_PROHIBIT;
774 } elseif ($ctxcapdepth === $rolecapdepth) {
776 } elseif ($ctxcapdepth < $rolecapdepth) {
778 $ctxcapdepth = $rolecapdepth;
779 } else { // ctxcaptdepth is deeper
783 // The most local RAs with a defined
784 // permission ($ctxcan) win, except
786 // NOTE: If we want the deepest RDEF to
787 // win regardless of the depth of the RA,
788 // change the elseif below to read
789 // ($can === 0 || $capdepth < $ctxcapdepth) {
790 if ($ctxcan === $CAP_PROHIBIT) {
793 } elseif ($can === 0) { // see note above
795 $capdepth = $ctxcapdepth;
802 // didn't find it as an explicit cap,
803 // but maybe the user can doanything in this context...
804 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
814 function aggregate_roles_from_accessdata($context, $accessdata) {
816 $path = $context->path
;
818 // build $contexts as a list of "paths" of the current
819 // contexts and parents with the order top-to-bottom
820 $contexts = array($path);
821 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
823 array_unshift($contexts, $path);
826 $cc = count($contexts);
829 // From the bottom up...
830 for ($n=$cc-1;$n>=0;$n--) {
831 $ctxp = $contexts[$n];
832 if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
833 // Found assignments on this leaf
834 $addroles = $accessdata['ra'][$ctxp];
835 $roles = array_merge($roles, $addroles);
839 return array_unique($roles);
843 * This is an easy to use function, combining has_capability() with require_course_login().
844 * And will call those where needed.
846 * It checks for a capability assertion being true. If it isn't
847 * then the page is terminated neatly with a standard error message.
849 * If the user is not logged in, or is using 'guest' access or other special "users,
850 * it provides a logon prompt.
852 * @param string $capability - name of the capability
853 * @param object $context - a context object (record from context table)
854 * @param integer $userid - a userid number
855 * @param bool $doanything - if false, ignore do anything
856 * @param string $errorstring - an errorstring
857 * @param string $stringfile - which stringfile to get it from
859 function require_capability($capability, $context, $userid=NULL, $doanything=true,
860 $errormessage='nopermissions', $stringfile='') {
864 /* Empty $userid means current user, if the current user is not logged in,
865 * then make sure they are (if needed).
866 * Originally there was a check for loaded permissions - it is not needed here.
867 * Context is now required parameter, the cached $CONTEXT was only hiding errors.
871 if (empty($userid)) {
872 if ($context->contextlevel
== CONTEXT_COURSE
) {
873 require_login($context->instanceid
);
875 } else if ($context->contextlevel
== CONTEXT_MODULE
) {
876 if (!$cm = get_record('course_modules', 'id', $context->instanceid
)) {
877 error('Incorrect module');
879 if (!$course = get_record('course', 'id', $cm->course
)) {
880 error('Incorrect course.');
882 require_course_login($course, true, $cm);
883 $errorlink = $CFG->wwwroot
.'/course/view.php?id='.$cm->course
;
885 } else if ($context->contextlevel
== CONTEXT_SYSTEM
) {
886 if (!empty($CFG->forcelogin
)) {
895 /// OK, if they still don't have the capability then print a nice error message
897 if (!has_capability($capability, $context, $userid, $doanything)) {
898 $capabilityname = get_capability_string($capability);
899 print_error($errormessage, $stringfile, $errorlink, $capabilityname);
904 * Get an array of courses (with magic extra bits)
905 * where the accessdata and in DB enrolments show
906 * that the cap requested is available.
908 * The main use is for get_my_courses().
912 * - $fields is an array of fieldnames to ADD
913 * so name the fields you really need, which will
914 * be added and uniq'd
916 * - the course records have $c->context which is a fully
917 * valid context object. Saves you a query per course!
919 * - the course records have $c->categorypath to make
920 * category lookups cheap
922 * - current implementation is split in -
924 * - if the user has the cap systemwide, stupidly
925 * grab *every* course for a capcheck. This eats
926 * a TON of bandwidth, specially on large sites
927 * with separate DBs...
929 * - otherwise, fetch "likely" courses with a wide net
930 * that should get us _cheaply_ at least the courses we need, and some
931 * we won't - we get courses that...
932 * - are in a category where user has the cap
933 * - or where use has a role-assignment (any kind)
934 * - or where the course has an override on for this cap
936 * - walk the courses recordset checking the caps oneach one
937 * the checks are all in memory and quite fast
938 * (though we could implement a specialised variant of the
939 * has_capability_in_accessdata() code to speed it up)
941 * @param string $capability - name of the capability
942 * @param array $accessdata - accessdata session array
943 * @param bool $doanything - if false, ignore do anything
944 * @param string $sort - sorting fields - prefix each fieldname with "c."
945 * @param array $fields - additional fields you are interested in...
946 * @param int $limit - set if you want to limit the number of courses
947 * @return array $courses - ordered array of course objects - see notes above
950 function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) {
954 // Slim base fields, let callers ask for what they need...
955 $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
957 if (!is_null($fields)) {
958 $fields = array_merge($basefields, $fields);
959 $fields = array_unique($fields);
961 $fields = $basefields;
963 $coursefields = 'c.' .implode(',c.', $fields);
967 $sort = "ORDER BY $sort";
970 $sysctx = get_context_instance(CONTEXT_SYSTEM
);
971 if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
973 // Apparently the user has the cap sitewide, so walk *every* course
974 // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
977 $sql = "SELECT $coursefields,
978 ctx.id AS ctxid, ctx.path AS ctxpath,
979 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
980 cc.path AS categorypath
981 FROM {$CFG->prefix}course c
982 JOIN {$CFG->prefix}course_categories cc
984 JOIN {$CFG->prefix}context ctx
985 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
987 $rs = get_recordset_sql($sql);
990 // narrow down where we have the caps to a few contexts
991 // this will be a combination of
992 // - courses where user has an explicit enrolment
993 // - courses that have an override (any status) on that capability
994 // - categories where user has the rights (granted status) on that capability
997 FROM {$CFG->prefix}context ctx
998 WHERE ctx.contextlevel=".CONTEXT_COURSECAT
."
1000 $rs = get_recordset_sql($sql);
1001 $catpaths = array();
1002 while ($catctx = rs_fetch_next_record($rs)) {
1003 if ($catctx->path
!= ''
1004 && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
1005 $catpaths[] = $catctx->path
;
1010 if (count($catpaths)) {
1011 $cc = count($catpaths);
1012 for ($n=0;$n<$cc;$n++
) {
1013 $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
1015 $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')';
1021 $capany = " OR rc.capability='moodle/site:doanything'";
1024 /// UNION 3 queries:
1025 /// - user role assignments in courses
1026 /// - user capability (override - any status) in courses
1027 /// - user right (granted status) in categories (optionally executed)
1028 /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
1029 /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
1031 SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath
1034 ctx.id AS ctxid, ctx.path AS ctxpath,
1035 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1036 cc.path AS categorypath
1037 FROM {$CFG->prefix}course c
1038 JOIN {$CFG->prefix}course_categories cc
1040 JOIN {$CFG->prefix}context ctx
1041 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1042 JOIN {$CFG->prefix}role_assignments ra
1043 ON (ra.contextid=ctx.id AND ra.userid=$userid)
1046 ctx.id AS ctxid, ctx.path AS ctxpath,
1047 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1048 cc.path AS categorypath
1049 FROM {$CFG->prefix}course c
1050 JOIN {$CFG->prefix}course_categories cc
1052 JOIN {$CFG->prefix}context ctx
1053 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1054 JOIN {$CFG->prefix}role_capabilities rc
1055 ON (rc.contextid=ctx.id AND (rc.capability='$cap' $capany)) ";
1057 if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too
1061 ctx.id AS ctxid, ctx.path AS ctxpath,
1062 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1063 cc.path AS categorypath
1064 FROM {$CFG->prefix}course c
1065 JOIN {$CFG->prefix}course_categories cc
1067 JOIN {$CFG->prefix}context ctx
1068 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
1072 /// Close the inline_view and join with courses table to get requested $coursefields
1075 INNER JOIN {$CFG->prefix}course c
1076 ON inline_view.id = c.id";
1078 /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
1080 " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause
1082 $rs = get_recordset_sql($sql);
1085 /// Confirm rights (granted capability) for each course returned
1087 $cc = 0; // keep count
1088 while ($c = rs_fetch_next_record($rs)) {
1089 // build the context obj
1090 $c = make_context_subobj($c);
1092 if (has_capability_in_accessdata($cap, $c->context
, $accessdata, $doanything)) {
1094 if ($limit > 0 && $cc++
> $limit) {
1105 * It will return a nested array showing role assignments
1106 * all relevant role capabilities for the user at
1107 * site/metacourse/course_category/course levels
1109 * We do _not_ delve deeper than courses because the number of
1110 * overrides at the module/block levels is HUGE.
1112 * [ra] => [/path/] = array(roleid, roleid)
1113 * [rdef] => [/path/:roleid][capability]=permission
1114 * [loaded] => array('/path', '/path')
1116 * @param $userid integer - the id of the user
1119 function get_user_access_sitewide($userid) {
1123 // this flag has not been set!
1124 // (not clean install, or upgraded successfully to 1.7 and up)
1125 if (empty($CFG->rolesactive
)) {
1129 /* Get in 3 cheap DB queries...
1130 * - role assignments - with role_caps
1131 * - relevant role caps
1132 * - above this user's RAs
1133 * - below this user's RAs - limited to course level
1136 $accessdata = array(); // named list
1137 $accessdata['ra'] = array();
1138 $accessdata['rdef'] = array();
1139 $accessdata['loaded'] = array();
1141 $sitectx = get_system_context();
1142 $base = '/'.$sitectx->id
;
1145 // Role assignments - and any rolecaps directly linked
1146 // because it's cheap to read rolecaps here over many
1149 $sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission
1150 FROM {$CFG->prefix}role_assignments ra
1151 JOIN {$CFG->prefix}context ctx
1152 ON ra.contextid=ctx.id
1153 LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc
1154 ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid)
1155 WHERE ra.userid = $userid AND ctx.contextlevel <= ".CONTEXT_COURSE
."
1156 ORDER BY ctx.depth, ctx.path, ra.roleid";
1157 $rs = get_recordset_sql($sql);
1159 // raparents collects paths & roles we need to walk up
1160 // the parenthood to build the rdef
1162 // the array will bulk up a bit with dups
1163 // which we'll later clear up
1165 $raparents = array();
1168 while ($ra = rs_fetch_next_record($rs)) {
1169 // RAs leafs are arrays to support multi
1170 // role assignments...
1171 if (!isset($accessdata['ra'][$ra->path
])) {
1172 $accessdata['ra'][$ra->path
] = array();
1174 // only add if is not a repeat caused
1175 // by capability join...
1176 // (this check is cheaper than in_array())
1177 if ($lastseen !== $ra->path
.':'.$ra->roleid
) {
1178 $lastseen = $ra->path
.':'.$ra->roleid
;
1179 array_push($accessdata['ra'][$ra->path
], $ra->roleid
);
1180 $parentids = explode('/', $ra->path
);
1181 array_shift($parentids); // drop empty leading "context"
1182 array_pop($parentids); // drop _this_ context
1184 if (isset($raparents[$ra->roleid
])) {
1185 $raparents[$ra->roleid
] = array_merge($raparents[$ra->roleid
],
1188 $raparents[$ra->roleid
] = $parentids;
1191 // Always add the roleded
1192 if (!empty($ra->capability
)) {
1193 $k = "{$ra->path}:{$ra->roleid}";
1194 $accessdata['rdef'][$k][$ra->capability
] = $ra->permission
;
1201 // Walk up the tree to grab all the roledefs
1202 // of interest to our user...
1203 // NOTE: we use a series of IN clauses here - which
1204 // might explode on huge sites with very convoluted nesting of
1205 // categories... - extremely unlikely that the number of categories
1206 // and roletypes is so large that we hit the limits of IN()
1208 foreach ($raparents as $roleid=>$contexts) {
1209 $contexts = implode(',', array_unique($contexts));
1210 if ($contexts ==! '') {
1211 $clauses[] = "(roleid=$roleid AND contextid IN ($contexts))";
1214 $clauses = implode(" OR ", $clauses);
1215 if ($clauses !== '') {
1216 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1217 FROM {$CFG->prefix}role_capabilities rc
1218 JOIN {$CFG->prefix}context ctx
1219 ON rc.contextid=ctx.id
1221 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1223 $rs = get_recordset_sql($sql);
1227 while ($rd = rs_fetch_next_record($rs)) {
1228 $k = "{$rd->path}:{$rd->roleid}";
1229 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1237 // Overrides for the role assignments IN SUBCONTEXTS
1238 // (though we still do _not_ go below the course level.
1240 // NOTE that the JOIN w sctx is with 3-way triangulation to
1241 // catch overrides to the applicable role in any subcontext, based
1242 // on the path field of the parent.
1244 $sql = "SELECT sctx.path, ra.roleid,
1245 ctx.path AS parentpath,
1246 rco.capability, rco.permission
1247 FROM {$CFG->prefix}role_assignments ra
1248 JOIN {$CFG->prefix}context ctx
1249 ON ra.contextid=ctx.id
1250 JOIN {$CFG->prefix}context sctx
1251 ON (sctx.path LIKE " . sql_concat('ctx.path',"'/%'"). " )
1252 JOIN {$CFG->prefix}role_capabilities rco
1253 ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1254 WHERE ra.userid = $userid
1255 AND sctx.contextlevel <= ".CONTEXT_COURSE
."
1256 ORDER BY sctx.depth, sctx.path, ra.roleid";
1258 $rs = get_recordset_sql($sql);
1260 while ($rd = rs_fetch_next_record($rs)) {
1261 $k = "{$rd->path}:{$rd->roleid}";
1262 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1271 * It add to the access ctrl array the data
1272 * needed by a user for a given context
1274 * @param $userid integer - the id of the user
1275 * @param $context context obj - needs path!
1276 * @param $accessdata array accessdata array
1278 function load_subcontext($userid, $context, &$accessdata) {
1284 /* Get the additional RAs and relevant rolecaps
1285 * - role assignments - with role_caps
1286 * - relevant role caps
1287 * - above this user's RAs
1288 * - below this user's RAs - limited to course level
1291 $base = "/" . SYSCONTEXTID
;
1294 // Replace $context with the target context we will
1295 // load. Normally, this will be a course context, but
1296 // may be a different top-level context.
1301 // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1302 // - BLOCK/MODULE/GROUP hanging from a course
1304 // For course contexts, we _already_ have the RAs
1305 // but the cost of re-fetching is minimal so we don't care.
1307 if ($context->contextlevel
!== CONTEXT_COURSE
1308 && $context->path
!== "$base/{$context->id}") {
1309 // Case BLOCK/MODULE/GROUP hanging from a course
1310 // Assumption: the course _must_ be our parent
1311 // If we ever see stuff nested further this needs to
1312 // change to do 1 query over the exploded path to
1313 // find out which one is the course
1314 $courses = explode('/',get_course_from_path($context->path
));
1315 $targetid = array_pop($courses);
1316 $context = get_context_instance_by_id($targetid);
1321 // Role assignments in the context and below
1323 $sql = "SELECT ctx.path, ra.roleid
1324 FROM {$CFG->prefix}role_assignments ra
1325 JOIN {$CFG->prefix}context ctx
1326 ON ra.contextid=ctx.id
1327 WHERE ra.userid = $userid
1328 AND (ctx.path = '{$context->path}' OR ctx.path LIKE '{$context->path}/%')
1329 ORDER BY ctx.depth, ctx.path, ra.roleid";
1330 $rs = get_recordset_sql($sql);
1333 // Read in the RAs, preventing duplicates
1335 $localroles = array();
1337 while ($ra = rs_fetch_next_record($rs)) {
1338 if (!isset($accessdata['ra'][$ra->path
])) {
1339 $accessdata['ra'][$ra->path
] = array();
1341 // only add if is not a repeat caused
1342 // by capability join...
1343 // (this check is cheaper than in_array())
1344 if ($lastseen !== $ra->path
.':'.$ra->roleid
) {
1345 $lastseen = $ra->path
.':'.$ra->roleid
;
1346 array_push($accessdata['ra'][$ra->path
], $ra->roleid
);
1347 array_push($localroles, $ra->roleid
);
1353 // Walk up and down the tree to grab all the roledefs
1354 // of interest to our user...
1357 // - we use IN() but the number of roles is very limited.
1359 $courseroles = aggregate_roles_from_accessdata($context, $accessdata);
1361 // Do we have any interesting "local" roles?
1362 $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1363 $wherelocalroles='';
1364 if (count($localroles)) {
1365 // Role defs for local roles in 'higher' contexts...
1366 $contexts = substr($context->path
, 1); // kill leading slash
1367 $contexts = str_replace('/', ',', $contexts);
1368 $localroleids = implode(',',$localroles);
1369 $wherelocalroles="OR (rc.roleid IN ({$localroleids})
1370 AND ctx.id IN ($contexts))" ;
1373 // We will want overrides for all of them
1375 if ($roleids = implode(',',array_merge($courseroles,$localroles))) {
1376 $whereroles = "rc.roleid IN ($roleids) AND";
1378 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1379 FROM {$CFG->prefix}role_capabilities rc
1380 JOIN {$CFG->prefix}context ctx
1381 ON rc.contextid=ctx.id
1383 (ctx.id={$context->id} OR ctx.path LIKE '{$context->path}/%'))
1385 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1387 $newrdefs = array();
1388 if ($rs = get_recordset_sql($sql)) {
1389 while ($rd = rs_fetch_next_record($rs)) {
1390 $k = "{$rd->path}:{$rd->roleid}";
1391 if (!array_key_exists($k, $newrdefs)) {
1392 $newrdefs[$k] = array();
1394 $newrdefs[$k][$rd->capability
] = $rd->permission
;
1398 debugging('Bad SQL encountered!');
1401 compact_rdefs($newrdefs);
1402 foreach ($newrdefs as $key=>$value) {
1403 $accessdata['rdef'][$key] =& $newrdefs[$key];
1406 // error_log("loaded {$context->path}");
1407 $accessdata['loaded'][] = $context->path
;
1411 * It add to the access ctrl array the data
1412 * needed by a role for a given context.
1414 * The data is added in the rdef key.
1416 * This role-centric function is useful for role_switching
1417 * and to get an overview of what a role gets under a
1418 * given context and below...
1420 * @param $roleid integer - the id of the user
1421 * @param $context context obj - needs path!
1422 * @param $accessdata accessdata array
1425 function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
1429 /* Get the relevant rolecaps into rdef
1430 * - relevant role caps
1431 * - at ctx and above
1435 if (is_null($accessdata)) {
1436 $accessdata = array(); // named list
1437 $accessdata['ra'] = array();
1438 $accessdata['rdef'] = array();
1439 $accessdata['loaded'] = array();
1442 $contexts = substr($context->path
, 1); // kill leading slash
1443 $contexts = str_replace('/', ',', $contexts);
1446 // Walk up and down the tree to grab all the roledefs
1447 // of interest to our role...
1449 // NOTE: we use an IN clauses here - which
1450 // might explode on huge sites with very convoluted nesting of
1451 // categories... - extremely unlikely that the number of nested
1452 // categories is so large that we hit the limits of IN()
1454 $sql = "SELECT ctx.path, rc.capability, rc.permission
1455 FROM {$CFG->prefix}role_capabilities rc
1456 JOIN {$CFG->prefix}context ctx
1457 ON rc.contextid=ctx.id
1458 WHERE rc.roleid=$roleid AND
1459 ( ctx.id IN ($contexts) OR
1460 ctx.path LIKE '{$context->path}/%' )
1461 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1463 $rs = get_recordset_sql($sql);
1464 while ($rd = rs_fetch_next_record($rs)) {
1465 $k = "{$rd->path}:{$roleid}";
1466 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1474 * Load accessdata for a user
1475 * into the $ACCESS global
1477 * Used by has_capability() - but feel free
1478 * to call it if you are about to run a BIG
1479 * cron run across a bazillion users.
1482 function load_user_accessdata($userid) {
1483 global $ACCESS,$CFG;
1485 $base = '/'.SYSCONTEXTID
;
1487 $accessdata = get_user_access_sitewide($userid);
1488 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
1490 // provide "default role" & set 'dr'
1492 if (!empty($CFG->defaultuserroleid
)) {
1493 $accessdata = get_role_access($CFG->defaultuserroleid
, $accessdata);
1494 if (!isset($accessdata['ra'][$base])) {
1495 $accessdata['ra'][$base] = array($CFG->defaultuserroleid
);
1497 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid
);
1499 $accessdata['dr'] = $CFG->defaultuserroleid
;
1503 // provide "default frontpage role"
1505 if (!empty($CFG->defaultfrontpageroleid
)) {
1506 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
1507 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid
, $accessdata);
1508 if (!isset($accessdata['ra'][$base])) {
1509 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid
);
1511 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid
);
1514 // for dirty timestamps in cron
1515 $accessdata['time'] = time();
1517 $ACCESS[$userid] = $accessdata;
1518 compact_rdefs($ACCESS[$userid]['rdef']);
1524 * Use shared copy of role definistions stored in $RDEFS;
1525 * @param array $rdefs array of role definitions in contexts
1527 function compact_rdefs(&$rdefs) {
1531 * This is a basic sharing only, we could also
1532 * use md5 sums of values. The main purpose is to
1533 * reduce mem in cron jobs - many users in $ACCESS array.
1536 foreach ($rdefs as $key => $value) {
1537 if (!array_key_exists($key, $RDEFS)) {
1538 $RDEFS[$key] = $rdefs[$key];
1540 $rdefs[$key] =& $RDEFS[$key];
1545 * A convenience function to completely load all the capabilities
1546 * for the current user. This is what gets called from complete_user_login()
1547 * for example. Call it only _after_ you've setup $USER and called
1548 * check_enrolment_plugins();
1551 function load_all_capabilities() {
1552 global $USER, $CFG, $DIRTYCONTEXTS;
1554 $base = '/'.SYSCONTEXTID
;
1556 if (isguestuser()) {
1557 $guest = get_guest_role();
1560 $USER->access
= get_role_access($guest->id
);
1561 // Put the ghost enrolment in place...
1562 $USER->access
['ra'][$base] = array($guest->id
);
1565 } else if (isloggedin()) {
1567 $accessdata = get_user_access_sitewide($USER->id
);
1570 // provide "default role" & set 'dr'
1572 if (!empty($CFG->defaultuserroleid
)) {
1573 $accessdata = get_role_access($CFG->defaultuserroleid
, $accessdata);
1574 if (!isset($accessdata['ra'][$base])) {
1575 $accessdata['ra'][$base] = array($CFG->defaultuserroleid
);
1577 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid
);
1579 $accessdata['dr'] = $CFG->defaultuserroleid
;
1582 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
1585 // provide "default frontpage role"
1587 if (!empty($CFG->defaultfrontpageroleid
)) {
1588 $base = '/'. SYSCONTEXTID
.'/'. $frontpagecontext->id
;
1589 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid
, $accessdata);
1590 if (!isset($accessdata['ra'][$base])) {
1591 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid
);
1593 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid
);
1596 $USER->access
= $accessdata;
1598 } else if (!empty($CFG->notloggedinroleid
)) {
1599 $USER->access
= get_role_access($CFG->notloggedinroleid
);
1600 $USER->access
['ra'][$base] = array($CFG->notloggedinroleid
);
1603 // Timestamp to read dirty context timestamps later
1604 $USER->access
['time'] = time();
1605 $DIRTYCONTEXTS = array();
1607 // Clear to force a refresh
1608 unset($USER->mycourses
);
1612 * A convenience function to completely reload all the capabilities
1613 * for the current user when roles have been updated in a relevant
1614 * context -- but PRESERVING switchroles and loginas.
1616 * That is - completely transparent to the user.
1618 * Note: rewrites $USER->access completely.
1621 function reload_all_capabilities() {
1624 // error_log("reloading");
1627 if (isset($USER->access
['rsw'])) {
1628 $sw = $USER->access
['rsw'];
1629 // error_log(print_r($sw,1));
1632 unset($USER->access
);
1633 unset($USER->mycourses
);
1635 load_all_capabilities();
1637 foreach ($sw as $path => $roleid) {
1638 $context = get_record('context', 'path', $path);
1639 role_switch($roleid, $context);
1645 * Adds a temp role to an accessdata array.
1647 * Useful for the "temporary guest" access
1648 * we grant to logged-in users.
1650 * Note - assumes a course context!
1653 function load_temp_role($context, $roleid, $accessdata) {
1658 // Load rdefs for the role in -
1660 // - all the parents
1661 // - and below - IOWs overrides...
1664 // turn the path into a list of context ids
1665 $contexts = substr($context->path
, 1); // kill leading slash
1666 $contexts = str_replace('/', ',', $contexts);
1668 $sql = "SELECT ctx.path,
1669 rc.capability, rc.permission
1670 FROM {$CFG->prefix}context ctx
1671 JOIN {$CFG->prefix}role_capabilities rc
1672 ON rc.contextid=ctx.id
1673 WHERE (ctx.id IN ($contexts)
1674 OR ctx.path LIKE '{$context->path}/%')
1675 AND rc.roleid = {$roleid}
1676 ORDER BY ctx.depth, ctx.path";
1677 $rs = get_recordset_sql($sql);
1678 while ($rd = rs_fetch_next_record($rs)) {
1679 $k = "{$rd->path}:{$roleid}";
1680 $accessdata['rdef'][$k][$rd->capability
] = $rd->permission
;
1685 // Say we loaded everything for the course context
1686 // - which we just did - if the user gets a proper
1687 // RA in this session, this data will need to be reloaded,
1688 // but that is handled by the complete accessdata reload
1690 array_push($accessdata['loaded'], $context->path
);
1695 if (isset($accessdata['ra'][$context->path
])) {
1696 array_push($accessdata['ra'][$context->path
], $roleid);
1698 $accessdata['ra'][$context->path
] = array($roleid);
1706 * Check all the login enrolment information for the given user object
1707 * by querying the enrolment plugins
1709 function check_enrolment_plugins(&$user) {
1712 static $inprogress; // To prevent this function being called more than once in an invocation
1714 if (!empty($inprogress[$user->id
])) {
1718 $inprogress[$user->id
] = true; // Set the flag
1720 require_once($CFG->dirroot
.'/enrol/enrol.class.php');
1722 if (!($plugins = explode(',', $CFG->enrol_plugins_enabled
))) {
1723 $plugins = array($CFG->enrol
);
1726 foreach ($plugins as $plugin) {
1727 $enrol = enrolment_factory
::factory($plugin);
1728 if (method_exists($enrol, 'setup_enrolments')) { /// Plugin supports Roles (Moodle 1.7 and later)
1729 $enrol->setup_enrolments($user);
1730 } else { /// Run legacy enrolment methods
1731 if (method_exists($enrol, 'get_student_courses')) {
1732 $enrol->get_student_courses($user);
1734 if (method_exists($enrol, 'get_teacher_courses')) {
1735 $enrol->get_teacher_courses($user);
1738 /// deal with $user->students and $user->teachers stuff
1739 unset($user->student
);
1740 unset($user->teacher
);
1745 unset($inprogress[$user->id
]); // Unset the flag
1749 * Installs the roles system.
1750 * This function runs on a fresh install as well as on an upgrade from the old
1751 * hard-coded student/teacher/admin etc. roles to the new roles system.
1753 function moodle_install_roles() {
1757 /// Create a system wide context for assignemnt.
1758 $systemcontext = $context = get_context_instance(CONTEXT_SYSTEM
);
1761 /// Create default/legacy roles and capabilities.
1762 /// (1 legacy capability per legacy role at system level).
1764 $adminrole = create_role(addslashes(get_string('administrator')), 'admin',
1765 addslashes(get_string('administratordescription')), 'moodle/legacy:admin');
1766 $coursecreatorrole = create_role(addslashes(get_string('coursecreators')), 'coursecreator',
1767 addslashes(get_string('coursecreatorsdescription')), 'moodle/legacy:coursecreator');
1768 $editteacherrole = create_role(addslashes(get_string('defaultcourseteacher')), 'editingteacher',
1769 addslashes(get_string('defaultcourseteacherdescription')), 'moodle/legacy:editingteacher');
1770 $noneditteacherrole = create_role(addslashes(get_string('noneditingteacher')), 'teacher',
1771 addslashes(get_string('noneditingteacherdescription')), 'moodle/legacy:teacher');
1772 $studentrole = create_role(addslashes(get_string('defaultcoursestudent')), 'student',
1773 addslashes(get_string('defaultcoursestudentdescription')), 'moodle/legacy:student');
1774 $guestrole = create_role(addslashes(get_string('guest')), 'guest',
1775 addslashes(get_string('guestdescription')), 'moodle/legacy:guest');
1776 $userrole = create_role(addslashes(get_string('authenticateduser')), 'user',
1777 addslashes(get_string('authenticateduserdescription')), 'moodle/legacy:user');
1779 /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
1781 if (!assign_capability('moodle/site:doanything', CAP_ALLOW
, $adminrole, $systemcontext->id
)) {
1782 error('Could not assign moodle/site:doanything to the admin role');
1784 if (!update_capabilities()) {
1785 error('Had trouble upgrading the core capabilities for the Roles System');
1788 /// Look inside user_admin, user_creator, user_teachers, user_students and
1789 /// assign above new roles. If a user has both teacher and student role,
1790 /// only teacher role is assigned. The assignment should be system level.
1792 $dbtables = $db->MetaTables('TABLES');
1794 /// Set up the progress bar
1796 $usertables = array('user_admins', 'user_coursecreators', 'user_teachers', 'user_students');
1798 $totalcount = $progresscount = 0;
1799 foreach ($usertables as $usertable) {
1800 if (in_array($CFG->prefix
.$usertable, $dbtables)) {
1801 $totalcount +
= count_records($usertable);
1805 print_progress(0, $totalcount, 5, 1, 'Processing role assignments');
1807 /// Upgrade the admins.
1808 /// Sort using id ASC, first one is primary admin.
1810 if (in_array($CFG->prefix
.'user_admins', $dbtables)) {
1811 if ($rs = get_recordset_sql('SELECT * from '.$CFG->prefix
.'user_admins ORDER BY ID ASC')) {
1812 while ($admin = rs_fetch_next_record($rs)) {
1813 role_assign($adminrole, $admin->userid
, 0, $systemcontext->id
);
1815 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1820 // This is a fresh install.
1824 /// Upgrade course creators.
1825 if (in_array($CFG->prefix
.'user_coursecreators', $dbtables)) {
1826 if ($rs = get_recordset('user_coursecreators')) {
1827 while ($coursecreator = rs_fetch_next_record($rs)) {
1828 role_assign($coursecreatorrole, $coursecreator->userid
, 0, $systemcontext->id
);
1830 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1837 /// Upgrade editting teachers and non-editting teachers.
1838 if (in_array($CFG->prefix
.'user_teachers', $dbtables)) {
1839 if ($rs = get_recordset('user_teachers')) {
1840 while ($teacher = rs_fetch_next_record($rs)) {
1842 // removed code here to ignore site level assignments
1843 // since the contexts are separated now
1845 // populate the user_lastaccess table
1846 $access = new object();
1847 $access->timeaccess
= $teacher->timeaccess
;
1848 $access->userid
= $teacher->userid
;
1849 $access->courseid
= $teacher->course
;
1850 insert_record('user_lastaccess', $access);
1852 // assign the default student role
1853 $coursecontext = get_context_instance(CONTEXT_COURSE
, $teacher->course
); // needs cache
1855 if ($teacher->authority
== 0) {
1861 if ($teacher->editall
) { // editting teacher
1862 role_assign($editteacherrole, $teacher->userid
, 0, $coursecontext->id
, $teacher->timestart
, $teacher->timeend
, $hiddenteacher, $teacher->enrol
, $teacher->timemodified
);
1864 role_assign($noneditteacherrole, $teacher->userid
, 0, $coursecontext->id
, $teacher->timestart
, $teacher->timeend
, $hiddenteacher, $teacher->enrol
, $teacher->timemodified
);
1867 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1874 /// Upgrade students.
1875 if (in_array($CFG->prefix
.'user_students', $dbtables)) {
1876 if ($rs = get_recordset('user_students')) {
1877 while ($student = rs_fetch_next_record($rs)) {
1879 // populate the user_lastaccess table
1880 $access = new object;
1881 $access->timeaccess
= $student->timeaccess
;
1882 $access->userid
= $student->userid
;
1883 $access->courseid
= $student->course
;
1884 insert_record('user_lastaccess', $access);
1886 // assign the default student role
1887 $coursecontext = get_context_instance(CONTEXT_COURSE
, $student->course
);
1888 role_assign($studentrole, $student->userid
, 0, $coursecontext->id
, $student->timestart
, $student->timeend
, 0, $student->enrol
, $student->time
);
1890 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1897 /// Upgrade guest (only 1 entry).
1898 if ($guestuser = get_record('user', 'username', 'guest')) {
1899 role_assign($guestrole, $guestuser->id
, 0, $systemcontext->id
);
1901 print_progress($totalcount, $totalcount, 5, 1, 'Processing role assignments');
1904 /// Insert the correct records for legacy roles
1905 allow_assign($adminrole, $adminrole);
1906 allow_assign($adminrole, $coursecreatorrole);
1907 allow_assign($adminrole, $noneditteacherrole);
1908 allow_assign($adminrole, $editteacherrole);
1909 allow_assign($adminrole, $studentrole);
1910 allow_assign($adminrole, $guestrole);
1912 allow_assign($coursecreatorrole, $noneditteacherrole);
1913 allow_assign($coursecreatorrole, $editteacherrole);
1914 allow_assign($coursecreatorrole, $studentrole);
1915 allow_assign($coursecreatorrole, $guestrole);
1917 allow_assign($editteacherrole, $noneditteacherrole);
1918 allow_assign($editteacherrole, $studentrole);
1919 allow_assign($editteacherrole, $guestrole);
1921 /// Set up default allow override matrix
1922 allow_override($adminrole, $adminrole);
1923 allow_override($adminrole, $coursecreatorrole);
1924 allow_override($adminrole, $noneditteacherrole);
1925 allow_override($adminrole, $editteacherrole);
1926 allow_override($adminrole, $studentrole);
1927 allow_override($adminrole, $guestrole);
1928 allow_override($adminrole, $userrole);
1931 //allow_override($editteacherrole, $noneditteacherrole);
1932 //allow_override($editteacherrole, $studentrole);
1933 //allow_override($editteacherrole, $guestrole);
1936 /// Delete the old user tables when we are done
1938 $tables = array('user_students', 'user_teachers', 'user_coursecreators', 'user_admins');
1939 foreach ($tables as $tablename) {
1940 $table = new XMLDBTable($tablename);
1941 if (table_exists($table)) {
1948 * Returns array of all legacy roles.
1950 function get_legacy_roles() {
1952 'admin' => 'moodle/legacy:admin',
1953 'coursecreator' => 'moodle/legacy:coursecreator',
1954 'editingteacher' => 'moodle/legacy:editingteacher',
1955 'teacher' => 'moodle/legacy:teacher',
1956 'student' => 'moodle/legacy:student',
1957 'guest' => 'moodle/legacy:guest',
1958 'user' => 'moodle/legacy:user'
1962 function get_legacy_type($roleid) {
1963 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
1964 $legacyroles = get_legacy_roles();
1967 foreach($legacyroles as $ltype=>$lcap) {
1968 $localoverride = get_local_override($roleid, $sitecontext->id
, $lcap);
1969 if (!empty($localoverride->permission
) and $localoverride->permission
== CAP_ALLOW
) {
1970 //choose first selected legacy capability - reset the rest
1971 if (empty($result)) {
1974 unassign_capability($lcap, $roleid);
1983 * Assign the defaults found in this capabality definition to roles that have
1984 * the corresponding legacy capabilities assigned to them.
1985 * @param $legacyperms - an array in the format (example):
1986 * 'guest' => CAP_PREVENT,
1987 * 'student' => CAP_ALLOW,
1988 * 'teacher' => CAP_ALLOW,
1989 * 'editingteacher' => CAP_ALLOW,
1990 * 'coursecreator' => CAP_ALLOW,
1991 * 'admin' => CAP_ALLOW
1992 * @return boolean - success or failure.
1994 function assign_legacy_capabilities($capability, $legacyperms) {
1996 $legacyroles = get_legacy_roles();
1998 foreach ($legacyperms as $type => $perm) {
2000 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
2002 if (!array_key_exists($type, $legacyroles)) {
2003 error('Incorrect legacy role definition for type: '.$type);
2006 if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW
)) {
2007 foreach ($roles as $role) {
2008 // Assign a site level capability.
2009 if (!assign_capability($capability, $perm, $role->id
, $systemcontext->id
)) {
2020 * Checks to see if a capability is a legacy capability.
2021 * @param $capabilityname
2024 function islegacy($capabilityname) {
2025 if (strpos($capabilityname, 'moodle/legacy') === 0) {
2034 /**********************************
2035 * Context Manipulation functions *
2036 **********************************/
2039 * Create a new context record for use by all roles-related stuff
2040 * assumes that the caller has done the homework.
2043 * @param $instanceid
2045 * @return object newly created context
2047 function create_context($contextlevel, $instanceid) {
2051 if ($contextlevel == CONTEXT_SYSTEM
) {
2052 return create_system_context();
2055 $context = new object();
2056 $context->contextlevel
= $contextlevel;
2057 $context->instanceid
= $instanceid;
2059 // Define $context->path based on the parent
2060 // context. In other words... Who is your daddy?
2061 $basepath = '/' . SYSCONTEXTID
;
2066 switch ($contextlevel) {
2067 case CONTEXT_COURSECAT
:
2068 $sql = "SELECT ctx.path, ctx.depth
2069 FROM {$CFG->prefix}context ctx
2070 JOIN {$CFG->prefix}course_categories cc
2071 ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT
.")
2072 WHERE cc.id={$instanceid}";
2073 if ($p = get_record_sql($sql)) {
2074 $basepath = $p->path
;
2075 $basedepth = $p->depth
;
2076 } else if ($category = get_record('course_categories', 'id', $instanceid)) {
2077 if (empty($category->parent
)) {
2078 // ok - this is a top category
2079 } else if ($parent = get_context_instance(CONTEXT_COURSECAT
, $category->parent
)) {
2080 $basepath = $parent->path
;
2081 $basedepth = $parent->depth
;
2083 // wrong parent category - no big deal, this can be fixed later
2088 // incorrect category id
2093 case CONTEXT_COURSE
:
2094 $sql = "SELECT ctx.path, ctx.depth
2095 FROM {$CFG->prefix}context ctx
2096 JOIN {$CFG->prefix}course c
2097 ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT
.")
2098 WHERE c.id={$instanceid} AND c.id !=" . SITEID
;
2099 if ($p = get_record_sql($sql)) {
2100 $basepath = $p->path
;
2101 $basedepth = $p->depth
;
2102 } else if ($course = get_record('course', 'id', $instanceid)) {
2103 if ($course->id
== SITEID
) {
2104 //ok - no parent category
2105 } else if ($parent = get_context_instance(CONTEXT_COURSECAT
, $course->category
)) {
2106 $basepath = $parent->path
;
2107 $basedepth = $parent->depth
;
2109 // wrong parent category of course - no big deal, this can be fixed later
2113 } else if ($instanceid == SITEID
) {
2114 // no errors for missing site course during installation
2117 // incorrect course id
2122 case CONTEXT_MODULE
:
2123 $sql = "SELECT ctx.path, ctx.depth
2124 FROM {$CFG->prefix}context ctx
2125 JOIN {$CFG->prefix}course_modules cm
2126 ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
2127 WHERE cm.id={$instanceid}";
2128 if ($p = get_record_sql($sql)) {
2129 $basepath = $p->path
;
2130 $basedepth = $p->depth
;
2131 } else if ($cm = get_record('course_modules', 'id', $instanceid)) {
2132 if ($parent = get_context_instance(CONTEXT_COURSE
, $cm->course
)) {
2133 $basepath = $parent->path
;
2134 $basedepth = $parent->depth
;
2136 // course does not exist - modules can not exist without a course
2140 // cm does not exist
2146 // Only non-pinned & course-page based
2147 $sql = "SELECT ctx.path, ctx.depth
2148 FROM {$CFG->prefix}context ctx
2149 JOIN {$CFG->prefix}block_instance bi
2150 ON (bi.pageid=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE
.")
2151 WHERE bi.id={$instanceid} AND bi.pagetype='course-view'";
2152 if ($p = get_record_sql($sql)) {
2153 $basepath = $p->path
;
2154 $basedepth = $p->depth
;
2155 } else if ($bi = get_record('block_instance', 'id', $instanceid)) {
2156 if ($bi->pagetype
!= 'course-view') {
2157 // ok - not a course block
2158 } else if ($parent = get_context_instance(CONTEXT_COURSE
, $bi->pageid
)) {
2159 $basepath = $parent->path
;
2160 $basedepth = $parent->depth
;
2162 // parent course does not exist - course blocks can not exist without a course
2166 // block does not exist
2171 // default to basepath
2175 // if grandparents unknown, maybe rebuild_context_path() will solve it later
2176 if ($basedepth != 0) {
2177 $context->depth
= $basedepth+
1;
2180 if ($result and $id = insert_record('context', $context)) {
2181 // can't set the full path till we know the id!
2182 if ($basedepth != 0 and !empty($basepath)) {
2183 set_field('context', 'path', $basepath.'/'. $id, 'id', $id);
2185 return get_context_instance_by_id($id);
2188 debugging('Error: could not insert new context level "'.
2189 s($contextlevel).'", instance "'.
2190 s($instanceid).'".');
2196 * This hacky function is needed because we can not change system context instanceid using normal upgrade routine.
2198 function get_system_context($cache=true) {
2199 static $cached = null;
2200 if ($cache and defined('SYSCONTEXTID')) {
2201 if (is_null($cached)) {
2202 $cached = new object();
2203 $cached->id
= SYSCONTEXTID
;
2204 $cached->contextlevel
= CONTEXT_SYSTEM
;
2205 $cached->instanceid
= 0;
2206 $cached->path
= '/'.SYSCONTEXTID
;
2212 if (!$context = get_record('context', 'contextlevel', CONTEXT_SYSTEM
)) {
2213 $context = new object();
2214 $context->contextlevel
= CONTEXT_SYSTEM
;
2215 $context->instanceid
= 0;
2216 $context->depth
= 1;
2217 $context->path
= NULL; //not known before insert
2219 if (!$context->id
= insert_record('context', $context)) {
2220 // better something than nothing - let's hope it will work somehow
2221 if (!defined('SYSCONTEXTID')) {
2222 define('SYSCONTEXTID', 1);
2224 debugging('Can not create system context');
2225 $context->id
= SYSCONTEXTID
;
2226 $context->path
= '/'.SYSCONTEXTID
;
2231 if (!isset($context->depth
) or $context->depth
!= 1 or $context->instanceid
!= 0 or $context->path
!= '/'.$context->id
) {
2232 $context->instanceid
= 0;
2233 $context->path
= '/'.$context->id
;
2234 $context->depth
= 1;
2235 update_record('context', $context);
2238 if (!defined('SYSCONTEXTID')) {
2239 define('SYSCONTEXTID', $context->id
);
2247 * Remove a context record and any dependent entries,
2248 * removes context from static context cache too
2250 * @param $instanceid
2252 * @return bool properly deleted
2254 function delete_context($contextlevel, $instanceid) {
2255 global $context_cache, $context_cache_id;
2257 // do not use get_context_instance(), because the related object might not exist,
2258 // or the context does not exist yet and it would be created now
2259 if ($context = get_record('context', 'contextlevel', $contextlevel, 'instanceid', $instanceid)) {
2260 $result = delete_records('role_assignments', 'contextid', $context->id
) &&
2261 delete_records('role_capabilities', 'contextid', $context->id
) &&
2262 delete_records('role_names', 'contextid', $context->id
) &&
2263 delete_records('context', 'id', $context->id
);
2265 // do not mark dirty contexts if parents unknown
2266 if (!is_null($context->path
) and $context->depth
> 0) {
2267 mark_context_dirty($context->path
);
2270 // purge static context cache if entry present
2271 unset($context_cache[$contextlevel][$instanceid]);
2272 unset($context_cache_id[$context->id
]);
2282 * Precreates all contexts including all parents
2283 * @param int $contextlevel, empty means all
2284 * @param bool $buildpaths update paths and depths
2285 * @param bool $feedback show sql feedback
2288 function create_contexts($contextlevel=null, $buildpaths=true, $feedback=false) {
2291 //make sure system context exists
2292 $syscontext = get_system_context(false);
2294 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2295 or $contextlevel == CONTEXT_COURSE
2296 or $contextlevel == CONTEXT_MODULE
2297 or $contextlevel == CONTEXT_BLOCK
) {
2298 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2299 SELECT ".CONTEXT_COURSECAT
.", cc.id
2300 FROM {$CFG->prefix}course_categories cc
2301 WHERE NOT EXISTS (SELECT 'x'
2302 FROM {$CFG->prefix}context cx
2303 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT
.")";
2304 execute_sql($sql, $feedback);
2308 if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2309 or $contextlevel == CONTEXT_MODULE
2310 or $contextlevel == CONTEXT_BLOCK
) {
2311 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2312 SELECT ".CONTEXT_COURSE
.", c.id
2313 FROM {$CFG->prefix}course c
2314 WHERE NOT EXISTS (SELECT 'x'
2315 FROM {$CFG->prefix}context cx
2316 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE
.")";
2317 execute_sql($sql, $feedback);
2321 if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
) {
2322 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2323 SELECT ".CONTEXT_MODULE
.", cm.id
2324 FROM {$CFG->prefix}course_modules cm
2325 WHERE NOT EXISTS (SELECT 'x'
2326 FROM {$CFG->prefix}context cx
2327 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE
.")";
2328 execute_sql($sql, $feedback);
2331 if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK
) {
2332 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2333 SELECT ".CONTEXT_BLOCK
.", bi.id
2334 FROM {$CFG->prefix}block_instance bi
2335 WHERE NOT EXISTS (SELECT 'x'
2336 FROM {$CFG->prefix}context cx
2337 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK
.")";
2338 execute_sql($sql, $feedback);
2341 if (empty($contextlevel) or $contextlevel == CONTEXT_USER
) {
2342 $sql = "INSERT INTO {$CFG->prefix}context (contextlevel, instanceid)
2343 SELECT ".CONTEXT_USER
.", u.id
2344 FROM {$CFG->prefix}user u
2346 AND NOT EXISTS (SELECT 'x'
2347 FROM {$CFG->prefix}context cx
2348 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER
.")";
2349 execute_sql($sql, $feedback);
2354 build_context_path(false, $feedback);
2359 * Remove stale context records
2363 function cleanup_contexts() {
2366 $sql = " SELECT c.contextlevel,
2367 c.instanceid AS instanceid
2368 FROM {$CFG->prefix}context c
2369 LEFT OUTER JOIN {$CFG->prefix}course_categories t
2370 ON c.instanceid = t.id
2371 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_COURSECAT
. "
2373 SELECT c.contextlevel,
2375 FROM {$CFG->prefix}context c
2376 LEFT OUTER JOIN {$CFG->prefix}course t
2377 ON c.instanceid = t.id
2378 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_COURSE
. "
2380 SELECT c.contextlevel,
2382 FROM {$CFG->prefix}context c
2383 LEFT OUTER JOIN {$CFG->prefix}course_modules t
2384 ON c.instanceid = t.id
2385 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_MODULE
. "
2387 SELECT c.contextlevel,
2389 FROM {$CFG->prefix}context c
2390 LEFT OUTER JOIN {$CFG->prefix}user t
2391 ON c.instanceid = t.id
2392 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_USER
. "
2394 SELECT c.contextlevel,
2396 FROM {$CFG->prefix}context c
2397 LEFT OUTER JOIN {$CFG->prefix}block_instance t
2398 ON c.instanceid = t.id
2399 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_BLOCK
. "
2401 SELECT c.contextlevel,
2403 FROM {$CFG->prefix}context c
2404 LEFT OUTER JOIN {$CFG->prefix}groups t
2405 ON c.instanceid = t.id
2406 WHERE t.id IS NULL AND c.contextlevel = " . CONTEXT_GROUP
. "
2408 if ($rs = get_recordset_sql($sql)) {
2411 while ($tx && $ctx = rs_fetch_next_record($rs)) {
2412 $tx = $tx && delete_context($ctx->contextlevel
, $ctx->instanceid
);
2427 * Preloads all contexts relating to a course: course, modules, and blocks.
2429 * @param int $courseid Course ID
2432 function preload_course_contexts($courseid) {
2433 global $context_cache, $context_cache_id, $CFG;
2435 // Users can call this multiple times without doing any harm
2436 static $preloadedcourses = array();
2437 if (array_key_exists($courseid, $preloadedcourses)) {
2441 $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2442 FROM {$CFG->prefix}course_modules cm
2443 JOIN {$CFG->prefix}context x ON x.instanceid=cm.id
2444 WHERE cm.course={$courseid}
2445 AND x.contextlevel=".CONTEXT_MODULE
."
2449 SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2450 FROM {$CFG->prefix}block_instance bi
2451 JOIN {$CFG->prefix}context x ON x.instanceid=bi.id
2452 WHERE bi.pageid={$courseid}
2453 AND bi.pagetype='course-view'
2454 AND x.contextlevel=".CONTEXT_BLOCK
."
2458 SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2459 FROM {$CFG->prefix}context x
2460 WHERE x.instanceid={$courseid}
2461 AND x.contextlevel=".CONTEXT_COURSE
."";
2463 $rs = get_recordset_sql($sql);
2464 while($context = rs_fetch_next_record($rs)) {
2465 $context_cache[$context->contextlevel
][$context->instanceid
] = $context;
2466 $context_cache_id[$context->id
] = $context;
2469 $preloadedcourses[$courseid] = true;
2473 * Get the context instance as an object. This function will create the
2474 * context instance if it does not exist yet.
2475 * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2476 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2477 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on.
2478 * @return object The context object.
2480 function get_context_instance($contextlevel, $instance=0) {
2482 global $context_cache, $context_cache_id, $CFG;
2483 static $allowed_contexts = array(CONTEXT_SYSTEM
, CONTEXT_USER
, CONTEXT_COURSECAT
, CONTEXT_COURSE
, CONTEXT_GROUP
, CONTEXT_MODULE
, CONTEXT_BLOCK
);
2485 if ($contextlevel === 'clearcache') {
2486 // TODO: Remove for v2.0
2487 // No longer needed, but we'll catch it to avoid erroring out on custom code.
2488 // This used to be a fix for MDL-9016
2489 // "Restoring into existing course, deleting first
2490 // deletes context and doesn't recreate it"
2494 /// System context has special cache
2495 if ($contextlevel == CONTEXT_SYSTEM
) {
2496 return get_system_context();
2499 /// check allowed context levels
2500 if (!in_array($contextlevel, $allowed_contexts)) {
2501 // fatal error, code must be fixed - probably typo or switched parameters
2502 error('Error: get_context_instance() called with incorrect context level "'.s($contextlevel).'"');
2505 if (!is_array($instance)) {
2507 if (isset($context_cache[$contextlevel][$instance])) { // Already cached
2508 return $context_cache[$contextlevel][$instance];
2511 /// Get it from the database, or create it
2512 if (!$context = get_record('context', 'contextlevel', $contextlevel, 'instanceid', $instance)) {
2513 $context = create_context($contextlevel, $instance);
2516 /// Only add to cache if context isn't empty.
2517 if (!empty($context)) {
2518 $context_cache[$contextlevel][$instance] = $context; // Cache it for later
2519 $context_cache_id[$context->id
] = $context; // Cache it for later
2526 /// ok, somebody wants to load several contexts to save some db queries ;-)
2527 $instances = $instance;
2530 foreach ($instances as $key=>$instance) {
2531 /// Check the cache first
2532 if (isset($context_cache[$contextlevel][$instance])) { // Already cached
2533 $result[$instance] = $context_cache[$contextlevel][$instance];
2534 unset($instances[$key]);
2540 if (count($instances) > 1) {
2541 $instanceids = implode(',', $instances);
2542 $instanceids = "instanceid IN ($instanceids)";
2544 $instance = reset($instances);
2545 $instanceids = "instanceid = $instance";
2548 if (!$contexts = get_records_sql("SELECT instanceid, id, contextlevel, path, depth
2549 FROM {$CFG->prefix}context
2550 WHERE contextlevel=$contextlevel AND $instanceids")) {
2551 $contexts = array();
2554 foreach ($instances as $instance) {
2555 if (isset($contexts[$instance])) {
2556 $context = $contexts[$instance];
2558 $context = create_context($contextlevel, $instance);
2561 if (!empty($context)) {
2562 $context_cache[$contextlevel][$instance] = $context; // Cache it for later
2563 $context_cache_id[$context->id
] = $context; // Cache it for later
2566 $result[$instance] = $context;
2575 * Get a context instance as an object, from a given context id.
2576 * @param mixed $id a context id or array of ids.
2577 * @return mixed object or array of the context object.
2579 function get_context_instance_by_id($id) {
2581 global $context_cache, $context_cache_id;
2583 if ($id == SYSCONTEXTID
) {
2584 return get_system_context();
2587 if (isset($context_cache_id[$id])) { // Already cached
2588 return $context_cache_id[$id];
2591 if ($context = get_record('context', 'id', $id)) { // Update the cache and return
2592 $context_cache[$context->contextlevel
][$context->instanceid
] = $context;
2593 $context_cache_id[$context->id
] = $context;
2602 * Get the local override (if any) for a given capability in a role in a context
2605 * @param $capability
2607 function get_local_override($roleid, $contextid, $capability) {
2608 return get_record('role_capabilities', 'roleid', $roleid, 'capability', $capability, 'contextid', $contextid);
2613 /************************************
2614 * DB TABLE RELATED FUNCTIONS *
2615 ************************************/
2618 * function that creates a role
2619 * @param name - role name
2620 * @param shortname - role short name
2621 * @param description - role description
2622 * @param legacy - optional legacy capability
2623 * @return id or false
2625 function create_role($name, $shortname, $description, $legacy='') {
2627 // check for duplicate role name
2629 if ($role = get_record('role','name', $name)) {
2630 error('there is already a role with this name!');
2633 if ($role = get_record('role','shortname', $shortname)) {
2634 error('there is already a role with this shortname!');
2637 $role = new object();
2638 $role->name
= $name;
2639 $role->shortname
= $shortname;
2640 $role->description
= $description;
2642 //find free sortorder number
2643 $role->sortorder
= count_records('role');
2644 while (get_record('role','sortorder', $role->sortorder
)) {
2645 $role->sortorder +
= 1;
2648 if (!$context = get_context_instance(CONTEXT_SYSTEM
)) {
2652 if ($id = insert_record('role', $role)) {
2654 assign_capability($legacy, CAP_ALLOW
, $id, $context->id
);
2657 /// By default, users with role:manage at site level
2658 /// should be able to assign users to this new role, and override this new role's capabilities
2660 // find all admin roles
2661 if ($adminroles = get_roles_with_capability('moodle/role:manage', CAP_ALLOW
, $context)) {
2662 // foreach admin role
2663 foreach ($adminroles as $arole) {
2664 // write allow_assign and allow_overrid
2665 allow_assign($arole->id
, $id);
2666 allow_override($arole->id
, $id);
2678 * function that deletes a role and cleanups up after it
2679 * @param roleid - id of role to delete
2682 function delete_role($roleid) {
2686 // mdl 10149, check if this is the last active admin role
2687 // if we make the admin role not deletable then this part can go
2689 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
2691 if ($role = get_record('role', 'id', $roleid)) {
2692 if (record_exists('role_capabilities', 'contextid', $systemcontext->id
, 'roleid', $roleid, 'capability', 'moodle/site:doanything')) {
2693 // deleting an admin role
2695 if ($adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW
, $systemcontext)) {
2696 foreach ($adminroles as $adminrole) {
2697 if ($adminrole->id
!= $roleid) {
2698 // some other admin role
2699 if (record_exists('role_assignments', 'roleid', $adminrole->id
, 'contextid', $systemcontext->id
)) {
2700 // found another admin role with at least 1 user assigned
2707 if ($status !== true) {
2708 error ('You can not delete this role because there is no other admin roles with users assigned');
2713 // first unssign all users
2714 if (!role_unassign($roleid)) {
2715 debugging("Error while unassigning all users from role with ID $roleid!");
2719 // cleanup all references to this role, ignore errors
2722 // MDL-10679 find all contexts where this role has an override
2723 $contexts = get_records_sql("SELECT contextid, contextid
2724 FROM {$CFG->prefix}role_capabilities
2725 WHERE roleid = $roleid");
2727 delete_records('role_capabilities', 'roleid', $roleid);
2729 delete_records('role_allow_assign', 'roleid', $roleid);
2730 delete_records('role_allow_assign', 'allowassign', $roleid);
2731 delete_records('role_allow_override', 'roleid', $roleid);
2732 delete_records('role_allow_override', 'allowoverride', $roleid);
2733 delete_records('role_names', 'roleid', $roleid);
2736 // finally delete the role itself
2737 // get this before the name is gone for logging
2738 $rolename = get_field('role', 'name', 'id', $roleid);
2740 if ($success and !delete_records('role', 'id', $roleid)) {
2741 debugging("Could not delete role record with ID $roleid!");
2746 add_to_log(SITEID
, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '', $USER->id
);
2753 * Function to write context specific overrides, or default capabilities.
2754 * @param module - string name
2755 * @param capability - string name
2756 * @param contextid - context id
2757 * @param roleid - role id
2758 * @param permission - int 1,-1 or -1000
2759 * should not be writing if permission is 0
2761 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
2765 if (empty($permission) ||
$permission == CAP_INHERIT
) { // if permission is not set
2766 unassign_capability($capability, $roleid, $contextid);
2770 $existing = get_record('role_capabilities', 'contextid', $contextid, 'roleid', $roleid, 'capability', $capability);
2772 if ($existing and !$overwrite) { // We want to keep whatever is there already
2777 $cap->contextid
= $contextid;
2778 $cap->roleid
= $roleid;
2779 $cap->capability
= $capability;
2780 $cap->permission
= $permission;
2781 $cap->timemodified
= time();
2782 $cap->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2785 $cap->id
= $existing->id
;
2786 return update_record('role_capabilities', $cap);
2788 $c = get_record('context', 'id', $contextid);
2789 return insert_record('role_capabilities', $cap);
2794 * Unassign a capability from a role.
2795 * @param $roleid - the role id
2796 * @param $capability - the name of the capability
2797 * @return boolean - success or failure
2799 function unassign_capability($capability, $roleid, $contextid=NULL) {
2801 if (isset($contextid)) {
2802 // delete from context rel, if this is the last override in this context
2803 $status = delete_records('role_capabilities', 'capability', $capability,
2804 'roleid', $roleid, 'contextid', $contextid);
2806 $status = delete_records('role_capabilities', 'capability', $capability,
2814 * Get the roles that have a given capability assigned to it. This function
2815 * does not resolve the actual permission of the capability. It just checks
2816 * for assignment only.
2817 * @param $capability - capability name (string)
2818 * @param $permission - optional, the permission defined for this capability
2819 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT
2820 * @return array or role objects
2822 function get_roles_with_capability($capability, $permission=NULL, $context='') {
2827 if ($contexts = get_parent_contexts($context)) {
2828 $listofcontexts = '('.implode(',', $contexts).')';
2830 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
2831 $listofcontexts = '('.$sitecontext->id
.')'; // must be site
2833 $contextstr = "AND (rc.contextid = '$context->id' OR rc.contextid IN $listofcontexts)";
2838 $selectroles = "SELECT r.*
2839 FROM {$CFG->prefix}role r,
2840 {$CFG->prefix}role_capabilities rc
2841 WHERE rc.capability = '$capability'
2842 AND rc.roleid = r.id $contextstr";
2844 if (isset($permission)) {
2845 $selectroles .= " AND rc.permission = '$permission'";
2847 return get_records_sql($selectroles);
2852 * This function makes a role-assignment (a role for a user or group in a particular context)
2853 * @param $roleid - the role of the id
2854 * @param $userid - userid
2855 * @param $groupid - group id
2856 * @param $contextid - id of the context
2857 * @param $timestart - time this assignment becomes effective
2858 * @param $timeend - time this assignemnt ceases to be effective
2860 * @return id - new id of the assigment
2862 function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden=0, $enrol='manual',$timemodified='') {
2865 /// Do some data validation
2867 if (empty($roleid)) {
2868 debugging('Role ID not provided');
2872 if (empty($userid) && empty($groupid)) {
2873 debugging('Either userid or groupid must be provided');
2877 if ($userid && !record_exists('user', 'id', $userid)) {
2878 debugging('User ID '.intval($userid).' does not exist!');
2882 if ($groupid && !groups_group_exists($groupid)) {
2883 debugging('Group ID '.intval($groupid).' does not exist!');
2887 if (!$context = get_context_instance_by_id($contextid)) {
2888 debugging('Context ID '.intval($contextid).' does not exist!');
2892 if (($timestart and $timeend) and ($timestart > $timeend)) {
2893 debugging('The end time can not be earlier than the start time');
2897 if (!$timemodified) {
2898 $timemodified = time();
2901 /// Check for existing entry
2903 $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id
, 'userid', $userid);
2905 $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id
, 'groupid', $groupid);
2908 if (empty($ra)) { // Create a new entry
2910 $ra->roleid
= $roleid;
2911 $ra->contextid
= $context->id
;
2912 $ra->userid
= $userid;
2913 $ra->hidden
= $hidden;
2914 $ra->enrol
= $enrol;
2915 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2916 /// by repeating queries with the same exact parameters in a 100 secs time window
2917 $ra->timestart
= round($timestart, -2);
2918 $ra->timeend
= $timeend;
2919 $ra->timemodified
= $timemodified;
2920 $ra->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2922 if (!$ra->id
= insert_record('role_assignments', $ra)) {
2926 } else { // We already have one, just update it
2928 $ra->hidden
= $hidden;
2929 $ra->enrol
= $enrol;
2930 /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2931 /// by repeating queries with the same exact parameters in a 100 secs time window
2932 $ra->timestart
= round($timestart, -2);
2933 $ra->timeend
= $timeend;
2934 $ra->timemodified
= $timemodified;
2935 $ra->modifierid
= empty($USER->id
) ?
0 : $USER->id
;
2937 if (!update_record('role_assignments', $ra)) {
2942 /// mark context as dirty - modules might use has_capability() in xxx_role_assing()
2943 /// again expensive, but needed
2944 mark_context_dirty($context->path
);
2946 if (!empty($USER->id
) && $USER->id
== $userid) {
2947 /// If the user is the current user, then do full reload of capabilities too.
2948 load_all_capabilities();
2951 /// Ask all the modules if anything needs to be done for this user
2952 if ($mods = get_list_of_plugins('mod')) {
2953 foreach ($mods as $mod) {
2954 include_once($CFG->dirroot
.'/mod/'.$mod.'/lib.php');
2955 $functionname = $mod.'_role_assign';
2956 if (function_exists($functionname)) {
2957 $functionname($userid, $context, $roleid);
2962 /// now handle metacourse role assignments if in course context
2963 if ($context->contextlevel
== CONTEXT_COURSE
) {
2964 if ($parents = get_records('course_meta', 'child_course', $context->instanceid
)) {
2965 foreach ($parents as $parent) {
2966 sync_metacourse($parent->parent_course
);
2971 events_trigger('role_assigned', $ra);
2978 * Deletes one or more role assignments. You must specify at least one parameter.
2983 * @param $enrol unassign only if enrolment type matches, NULL means anything
2984 * @return boolean - success or failure
2986 function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NULL) {
2988 require_once($CFG->dirroot
.'/group/lib.php');
2992 $args = array('roleid', 'userid', 'groupid', 'contextid');
2994 foreach ($args as $arg) {
2996 $select[] = $arg.' = '.$
$arg;
2999 if (!empty($enrol)) {
3000 $select[] = "enrol='$enrol'";
3004 if ($ras = get_records_select('role_assignments', implode(' AND ', $select))) {
3005 $mods = get_list_of_plugins('mod');
3006 foreach($ras as $ra) {
3008 /// infinite loop protection when deleting recursively
3009 if (!$ra = get_record('role_assignments', 'id', $ra->id
)) {
3012 if (delete_records('role_assignments', 'id', $ra->id
)) {
3018 if (!$context = get_context_instance_by_id($ra->contextid
)) {
3019 // strange error, not much to do
3023 /* mark contexts as dirty here, because we need the refreshed
3024 * caps bellow to delete group membership and user_lastaccess!
3025 * and yes, this is very expensive for bulk operations :-(
3027 mark_context_dirty($context->path
);
3029 /// If the user is the current user, then do full reload of capabilities too.
3030 if (!empty($USER->id
) && $USER->id
== $ra->userid
) {
3031 load_all_capabilities();
3034 /// Ask all the modules if anything needs to be done for this user
3035 foreach ($mods as $mod) {
3036 include_once($CFG->dirroot
.'/mod/'.$mod.'/lib.php');
3037 $functionname = $mod.'_role_unassign';
3038 if (function_exists($functionname)) {
3039 $functionname($ra->userid
, $context); // watch out, $context might be NULL if something goes wrong
3043 /// now handle metacourse role unassigment and removing from goups if in course context
3044 if ($context->contextlevel
== CONTEXT_COURSE
) {
3046 // cleanup leftover course groups/subscriptions etc when user has
3047 // no capability to view course
3048 // this may be slow, but this is the proper way of doing it
3049 if (!has_capability('moodle/course:view', $context, $ra->userid
)) {
3050 // remove from groups
3051 groups_delete_group_members($context->instanceid
, $ra->userid
);
3053 // delete lastaccess records
3054 delete_records('user_lastaccess', 'userid', $ra->userid
, 'courseid', $context->instanceid
);
3057 //unassign roles in metacourses if needed
3058 if ($parents = get_records('course_meta', 'child_course', $context->instanceid
)) {
3059 foreach ($parents as $parent) {
3060 sync_metacourse($parent->parent_course
);
3066 events_trigger('role_unassigned', $ra);
3076 * A convenience function to take care of the common case where you
3077 * just want to enrol someone using the default role into a course
3079 * @param object $course
3080 * @param object $user
3081 * @param string $enrol - the plugin used to do this enrolment
3083 function enrol_into_course($course, $user, $enrol) {
3085 $timestart = time();
3086 // remove time part from the timestamp and keep only the date part
3087 $timestart = make_timestamp(date('Y', $timestart), date('m', $timestart), date('d', $timestart), 0, 0, 0);
3088 if ($course->enrolperiod
) {
3089 $timeend = $timestart +
$course->enrolperiod
;
3094 if ($role = get_default_course_role($course)) {
3096 $context = get_context_instance(CONTEXT_COURSE
, $course->id
);
3098 if (!role_assign($role->id
, $user->id
, 0, $context->id
, $timestart, $timeend, 0, $enrol)) {
3102 // force accessdata refresh for users visiting this context...
3103 mark_context_dirty($context->path
);
3105 email_welcome_message_to_user($course, $user);
3107 add_to_log($course->id
, 'course', 'enrol',
3108 'view.php?id='.$course->id
, $course->id
);
3117 * Loads the capability definitions for the component (from file). If no
3118 * capabilities are defined for the component, we simply return an empty array.
3119 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3120 * @return array of capabilities
3122 function load_capability_def($component) {
3125 if ($component == 'moodle') {
3126 $defpath = $CFG->libdir
.'/db/access.php';
3127 $varprefix = 'moodle';
3129 $compparts = explode('/', $component);
3131 if ($compparts[0] == 'report') {
3132 $defpath = $CFG->dirroot
.'/'.$CFG->admin
.'/report/'.$compparts[1].'/db/access.php';
3133 $varprefix = $compparts[0].'_'.$compparts[1];
3135 } else if ($compparts[0] == 'block') {
3136 // Blocks are an exception. Blocks directory is 'blocks', and not
3137 // 'block'. So we need to jump through hoops.
3138 $defpath = $CFG->dirroot
.'/'.$compparts[0].
3139 's/'.$compparts[1].'/db/access.php';
3140 $varprefix = $compparts[0].'_'.$compparts[1];
3142 } else if ($compparts[0] == 'format') {
3143 // Similar to the above, course formats are 'format' while they
3144 // are stored in 'course/format'.
3145 $defpath = $CFG->dirroot
.'/course/'.$component.'/db/access.php';
3146 $varprefix = $compparts[0].'_'.$compparts[1];
3148 } else if ($compparts[0] == 'gradeimport') {
3149 $defpath = $CFG->dirroot
.'/grade/import/'.$compparts[1].'/db/access.php';
3150 $varprefix = $compparts[0].'_'.$compparts[1];
3152 } else if ($compparts[0] == 'gradeexport') {
3153 $defpath = $CFG->dirroot
.'/grade/export/'.$compparts[1].'/db/access.php';
3154 $varprefix = $compparts[0].'_'.$compparts[1];
3156 } else if ($compparts[0] == 'gradereport') {
3157 $defpath = $CFG->dirroot
.'/grade/report/'.$compparts[1].'/db/access.php';
3158 $varprefix = $compparts[0].'_'.$compparts[1];
3160 } else if ($compparts[0] == 'coursereport') {
3161 $defpath = $CFG->dirroot
.'/course/report/'.$compparts[1].'/db/access.php';
3162 $varprefix = $compparts[0].'_'.$compparts[1];
3165 $defpath = $CFG->dirroot
.'/'.$component.'/db/access.php';
3166 $varprefix = str_replace('/', '_', $component);
3169 $capabilities = array();
3171 if (file_exists($defpath)) {
3173 $capabilities = $
{$varprefix.'_capabilities'};
3175 return $capabilities;
3180 * Gets the capabilities that have been cached in the database for this
3182 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3183 * @return array of capabilities
3185 function get_cached_capabilities($component='moodle') {
3186 if ($component == 'moodle') {
3187 $storedcaps = get_records_select('capabilities',
3188 "name LIKE 'moodle/%:%'");
3189 } else if ($component == 'local') {
3190 $storedcaps = get_records_select('capabilities',
3191 "name LIKE 'moodle/local:%'");
3193 $storedcaps = get_records_select('capabilities',
3194 "name LIKE '$component:%'");
3200 * Returns default capabilities for given legacy role type.
3202 * @param string legacy role name
3205 function get_default_capabilities($legacyrole) {
3206 if (!$allcaps = get_records('capabilities')) {
3207 error('Error: no capabilitites defined!');
3210 $defaults = array();
3211 $components = array();
3212 foreach ($allcaps as $cap) {
3213 if (!in_array($cap->component
, $components)) {
3214 $components[] = $cap->component
;
3215 $alldefs = array_merge($alldefs, load_capability_def($cap->component
));
3218 foreach($alldefs as $name=>$def) {
3219 if (isset($def['legacy'][$legacyrole])) {
3220 $defaults[$name] = $def['legacy'][$legacyrole];
3225 $defaults['moodle/legacy:'.$legacyrole] = CAP_ALLOW
;
3226 if ($legacyrole == 'admin') {
3227 $defaults['moodle/site:doanything'] = CAP_ALLOW
;
3233 * Reset role capabilitites to default according to selected legacy capability.
3234 * If several legacy caps selected, use the first from get_default_capabilities.
3235 * If no legacy selected, removes all capabilities.
3237 * @param int @roleid
3239 function reset_role_capabilities($roleid) {
3240 $sitecontext = get_context_instance(CONTEXT_SYSTEM
);
3241 $legacyroles = get_legacy_roles();
3243 $defaultcaps = array();
3244 foreach($legacyroles as $ltype=>$lcap) {
3245 $localoverride = get_local_override($roleid, $sitecontext->id
, $lcap);
3246 if (!empty($localoverride->permission
) and $localoverride->permission
== CAP_ALLOW
) {
3247 //choose first selected legacy capability
3248 $defaultcaps = get_default_capabilities($ltype);
3253 delete_records('role_capabilities', 'roleid', $roleid);
3254 if (!empty($defaultcaps)) {
3255 foreach($defaultcaps as $cap=>$permission) {
3256 assign_capability($cap, $permission, $roleid, $sitecontext->id
);
3262 * Updates the capabilities table with the component capability definitions.
3263 * If no parameters are given, the function updates the core moodle
3266 * Note that the absence of the db/access.php capabilities definition file
3267 * will cause any stored capabilities for the component to be removed from
3270 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3273 function update_capabilities($component='moodle') {
3275 $storedcaps = array();
3277 $filecaps = load_capability_def($component);
3278 $cachedcaps = get_cached_capabilities($component);
3280 foreach ($cachedcaps as $cachedcap) {
3281 array_push($storedcaps, $cachedcap->name
);
3282 // update risk bitmasks and context levels in existing capabilities if needed
3283 if (array_key_exists($cachedcap->name
, $filecaps)) {
3284 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name
])) {
3285 $filecaps[$cachedcap->name
]['riskbitmask'] = 0; // no risk if not specified
3287 if ($cachedcap->riskbitmask
!= $filecaps[$cachedcap->name
]['riskbitmask']) {
3288 $updatecap = new object();
3289 $updatecap->id
= $cachedcap->id
;
3290 $updatecap->riskbitmask
= $filecaps[$cachedcap->name
]['riskbitmask'];
3291 if (!update_record('capabilities', $updatecap)) {
3296 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name
])) {
3297 $filecaps[$cachedcap->name
]['contextlevel'] = 0; // no context level defined
3299 if ($cachedcap->contextlevel
!= $filecaps[$cachedcap->name
]['contextlevel']) {
3300 $updatecap = new object();
3301 $updatecap->id
= $cachedcap->id
;
3302 $updatecap->contextlevel
= $filecaps[$cachedcap->name
]['contextlevel'];
3303 if (!update_record('capabilities', $updatecap)) {
3311 // Are there new capabilities in the file definition?
3314 foreach ($filecaps as $filecap => $def) {
3316 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3317 if (!array_key_exists('riskbitmask', $def)) {
3318 $def['riskbitmask'] = 0; // no risk if not specified
3320 $newcaps[$filecap] = $def;
3323 // Add new capabilities to the stored definition.
3324 foreach ($newcaps as $capname => $capdef) {
3325 $capability = new object;
3326 $capability->name
= $capname;
3327 $capability->captype
= $capdef['captype'];
3328 $capability->contextlevel
= $capdef['contextlevel'];
3329 $capability->component
= $component;
3330 $capability->riskbitmask
= $capdef['riskbitmask'];
3332 if (!insert_record('capabilities', $capability, false, 'id')) {
3337 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3338 if ($rolecapabilities = get_records('role_capabilities', 'capability', $capdef['clonepermissionsfrom'])){
3339 foreach ($rolecapabilities as $rolecapability){
3340 //assign_capability will update rather than insert if capability exists
3341 if (!assign_capability($capname, $rolecapability->permission
,
3342 $rolecapability->roleid
, $rolecapability->contextid
, true)){
3343 notify('Could not clone capabilities for '.$capname);
3347 // Do we need to assign the new capabilities to roles that have the
3348 // legacy capabilities moodle/legacy:* as well?
3349 // we ignore legacy key if we have cloned permissions
3350 } else if (isset($capdef['legacy']) && is_array($capdef['legacy']) &&
3351 !assign_legacy_capabilities($capname, $capdef['legacy'])) {
3352 notify('Could not assign legacy capabilities for '.$capname);
3355 // Are there any capabilities that have been removed from the file
3356 // definition that we need to delete from the stored capabilities and
3357 // role assignments?
3358 capabilities_cleanup($component, $filecaps);
3360 // reset static caches
3361 is_valid_capability('reset', false);
3368 * Deletes cached capabilities that are no longer needed by the component.
3369 * Also unassigns these capabilities from any roles that have them.
3370 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3371 * @param $newcapdef - array of the new capability definitions that will be
3372 * compared with the cached capabilities
3373 * @return int - number of deprecated capabilities that have been removed
3375 function capabilities_cleanup($component, $newcapdef=NULL) {
3379 if ($cachedcaps = get_cached_capabilities($component)) {
3380 foreach ($cachedcaps as $cachedcap) {
3381 if (empty($newcapdef) ||
3382 array_key_exists($cachedcap->name
, $newcapdef) === false) {
3384 // Remove from capabilities cache.
3385 if (!delete_records('capabilities', 'name', $cachedcap->name
)) {
3386 error('Could not delete deprecated capability '.$cachedcap->name
);
3390 // Delete from roles.
3391 if($roles = get_roles_with_capability($cachedcap->name
)) {
3392 foreach($roles as $role) {
3393 if (!unassign_capability($cachedcap->name
, $role->id
)) {
3394 error('Could not unassign deprecated capability '.
3395 $cachedcap->name
.' from role '.$role->name
);
3402 return $removedcount;
3413 * prints human readable context identifier.
3415 function print_context_name($context, $withprefix = true, $short = false) {
3418 switch ($context->contextlevel
) {
3420 case CONTEXT_SYSTEM
: // by now it's a definite an inherit
3421 $name = get_string('coresystem');
3425 if ($user = get_record('user', 'id', $context->instanceid
)) {
3427 $name = get_string('user').': ';
3429 $name .= fullname($user);
3433 case CONTEXT_COURSECAT
: // Coursecat -> coursecat or site
3434 if ($category = get_record('course_categories', 'id', $context->instanceid
)) {
3436 $name = get_string('category').': ';
3438 $name .=format_string($category->name
);
3442 case CONTEXT_COURSE
: // 1 to 1 to course cat
3443 if ($context->instanceid
== SITEID
) {
3444 $name = get_string('frontpage', 'admin');
3446 if ($course = get_record('course', 'id', $context->instanceid
)) {
3448 $name = get_string('course').': ';
3451 $name .= format_string($course->shortname
);
3453 $name .= format_string($course->fullname
);
3459 case CONTEXT_GROUP
: // 1 to 1 to course
3460 if ($name = groups_get_group_name($context->instanceid
)) {
3462 $name = get_string('group').': '. $name;
3467 case CONTEXT_MODULE
: // 1 to 1 to course
3468 if ($cm = get_record('course_modules','id',$context->instanceid
)) {
3469 if ($module = get_record('modules','id',$cm->module
)) {
3470 if ($mod = get_record($module->name
, 'id', $cm->instance
)) {
3472 $name = get_string('activitymodule').': ';
3474 $name .= $mod->name
;
3480 case CONTEXT_BLOCK
: // not necessarily 1 to 1 to course
3481 if ($blockinstance = get_record('block_instance','id',$context->instanceid
)) {
3482 if ($block = get_record('block','id',$blockinstance->blockid
)) {
3484 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3485 require_once("$CFG->dirroot/blocks/$block->name/block_$block->name.php");
3486 $blockname = "block_$block->name";
3487 if ($blockobject = new $blockname()) {
3489 $name = get_string('block').': ';
3491 $name .= $blockobject->title
;
3498 error ('This is an unknown context (' . $context->contextlevel
. ') in print_context_name!');
3507 * Extracts the relevant capabilities given a contextid.
3508 * All case based, example an instance of forum context.
3509 * Will fetch all forum related capabilities, while course contexts
3510 * Will fetch all capabilities
3511 * @param object context
3515 * `name` varchar(150) NOT NULL,
3516 * `captype` varchar(50) NOT NULL,
3517 * `contextlevel` int(10) NOT NULL,
3518 * `component` varchar(100) NOT NULL,
3520 function fetch_context_capabilities($context) {
3524 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
3526 switch ($context->contextlevel
) {
3528 case CONTEXT_SYSTEM
: // all
3530 FROM {$CFG->prefix}capabilities";
3534 $extracaps = array('moodle/grade:viewall');
3535 foreach ($extracaps as $key=>$value) {
3536 $extracaps[$key]= "'$value'";
3538 $extra = implode(',', $extracaps);
3540 FROM {$CFG->prefix}capabilities
3541 WHERE contextlevel = ".CONTEXT_USER
."
3542 OR name IN ($extra)";
3545 case CONTEXT_COURSECAT
: // course category context and bellow
3547 FROM {$CFG->prefix}capabilities
3548 WHERE contextlevel IN (".CONTEXT_COURSECAT
.",".CONTEXT_COURSE
.",".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")";
3551 case CONTEXT_COURSE
: // course context and bellow
3553 FROM {$CFG->prefix}capabilities
3554 WHERE contextlevel IN (".CONTEXT_COURSE
.",".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")";
3557 case CONTEXT_MODULE
: // mod caps
3558 $cm = get_record('course_modules', 'id', $context->instanceid
);
3559 $module = get_record('modules', 'id', $cm->module
);
3562 $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
3563 if (file_exists($modfile)) {
3564 include_once($modfile);
3565 $modfunction = $module->name
.'_get_extra_capabilities';
3566 if (function_exists($modfunction)) {
3567 if ($extracaps = $modfunction()) {
3568 foreach ($extracaps as $key=>$value) {
3569 $extracaps[$key]= "'$value'";
3571 $extra = implode(',', $extracaps);
3572 $extra = "OR name IN ($extra)";
3578 FROM {$CFG->prefix}capabilities
3579 WHERE (contextlevel = ".CONTEXT_MODULE
."
3580 AND component = 'mod/$module->name')
3584 case CONTEXT_BLOCK
: // block caps
3585 $cb = get_record('block_instance', 'id', $context->instanceid
);
3586 $block = get_record('block', 'id', $cb->blockid
);
3589 if ($blockinstance = block_instance($block->name
)) {
3590 if ($extracaps = $blockinstance->get_extra_capabilities()) {
3591 foreach ($extracaps as $key=>$value) {
3592 $extracaps[$key]= "'$value'";
3594 $extra = implode(',', $extracaps);
3595 $extra = "OR name IN ($extra)";
3600 FROM {$CFG->prefix}capabilities
3601 WHERE (contextlevel = ".CONTEXT_BLOCK
."
3602 AND component = 'block/$block->name')
3610 if (!$records = get_records_sql($SQL.' '.$sort)) {
3619 * This function pulls out all the resolved capabilities (overrides and
3620 * defaults) of a role used in capability overrides in contexts at a given
3622 * @param obj $context
3623 * @param int $roleid
3624 * @param bool self - if set to true, resolve till this level, else stop at immediate parent level
3627 function role_context_capabilities($roleid, $context, $cap='') {
3630 $contexts = get_parent_contexts($context);
3631 $contexts[] = $context->id
;
3632 $contexts = '('.implode(',', $contexts).')';
3635 $search = " AND rc.capability = '$cap' ";
3641 FROM {$CFG->prefix}role_capabilities rc,
3642 {$CFG->prefix}context c
3643 WHERE rc.contextid in $contexts
3644 AND rc.roleid = $roleid
3645 AND rc.contextid = c.id $search
3646 ORDER BY c.contextlevel DESC,
3647 rc.capability DESC";
3649 $capabilities = array();
3651 if ($records = get_records_sql($SQL)) {
3652 // We are traversing via reverse order.
3653 foreach ($records as $record) {
3654 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
3655 if (!isset($capabilities[$record->capability
]) ||
$record->permission
<-500) {
3656 $capabilities[$record->capability
] = $record->permission
;
3660 return $capabilities;
3664 * Recursive function which, given a context, find all parent context ids,
3665 * and return the array in reverse order, i.e. parent first, then grand
3668 * @param object $context
3671 function get_parent_contexts($context) {
3673 if ($context->path
== '') {
3677 $parentcontexts = substr($context->path
, 1); // kill leading slash
3678 $parentcontexts = explode('/', $parentcontexts);
3679 array_pop($parentcontexts); // and remove its own id
3681 return array_reverse($parentcontexts);
3685 * Return the id of the parent of this context, or false if there is no parent (only happens if this
3686 * is the site context.)
3688 * @param object $context
3689 * @return integer the id of the parent context.
3691 function get_parent_contextid($context) {
3692 $parentcontexts = get_parent_contexts($context);
3693 if (count($parentcontexts) == 0) {
3696 return array_shift($parentcontexts);
3700 * @param object $context a context object.
3701 * @return true if this context is the front page context, or a context inside it,
3704 function is_inside_frontpage($context) {
3705 $frontpagecontext = get_context_instance(CONTEXT_COURSE
, SITEID
);
3706 return strpos($context->path
. '/', $frontpagecontext->path
. '/') === 0;
3710 * Recursive function which, given a context, find all its children context ids.
3712 * When called for a course context, it will return the modules and blocks
3713 * displayed in the course page.
3715 * For course category contexts it will return categories and courses. It will
3716 * NOT recurse into courses - if you want to do that, call it on the returned
3719 * If called on a course context it _will_ populate the cache with the appropriate
3722 * @param object $context.
3723 * @return array of child records
3725 function get_child_contexts($context) {
3727 global $CFG, $context_cache;
3729 // We *MUST* populate the context_cache as the callers
3730 // will probably ask for the full record anyway soon after
3731 // soon after calling us ;-)
3733 switch ($context->contextlevel
) {
3740 case CONTEXT_MODULE
:
3750 case CONTEXT_COURSE
:
3752 // - module instances - easy
3754 // - blocks assigned to the course-view page explicitly - easy
3755 // - blocks pinned (note! we get all of them here, regardless of vis)
3756 $sql = " SELECT ctx.*
3757 FROM {$CFG->prefix}context ctx
3758 WHERE ctx.path LIKE '{$context->path}/%'
3759 AND ctx.contextlevel IN (".CONTEXT_MODULE
.",".CONTEXT_BLOCK
.")
3762 FROM {$CFG->prefix}context ctx
3763 JOIN {$CFG->prefix}groups g
3764 ON (ctx.instanceid=g.id AND ctx.contextlevel=".CONTEXT_GROUP
.")
3765 WHERE g.courseid={$context->instanceid}
3768 FROM {$CFG->prefix}context ctx
3769 JOIN {$CFG->prefix}block_pinned b
3770 ON (ctx.instanceid=b.blockid AND ctx.contextlevel=".CONTEXT_BLOCK
.")
3771 WHERE b.pagetype='course-view'
3773 $rs = get_recordset_sql($sql);
3775 while ($rec = rs_fetch_next_record($rs)) {
3776 $records[$rec->id
] = $rec;
3777 $context_cache[$rec->contextlevel
][$rec->instanceid
] = $rec;
3783 case CONTEXT_COURSECAT
:
3787 $sql = " SELECT ctx.*
3788 FROM {$CFG->prefix}context ctx
3789 WHERE ctx.path LIKE '{$context->path}/%'
3790 AND ctx.contextlevel IN (".CONTEXT_COURSECAT
.",".CONTEXT_COURSE
.")
3792 $rs = get_recordset_sql($sql);
3794 while ($rec = rs_fetch_next_record($rs)) {
3795 $records[$rec->id
] = $rec;
3796 $context_cache[$rec->contextlevel
][$rec->instanceid
] = $rec;
3807 case CONTEXT_SYSTEM
:
3808 // Just get all the contexts except for CONTEXT_SYSTEM level
3809 // and hope we don't OOM in the process - don't cache
3810 $sql = 'SELECT c.*'.
3811 'FROM '.$CFG->prefix
.'context c '.
3812 'WHERE contextlevel != '.CONTEXT_SYSTEM
;
3814 return get_records_sql($sql);
3818 error('This is an unknown context (' . $context->contextlevel
. ') in get_child_contexts!');
3825 * Gets a string for sql calls, searching for stuff in this context or above
3826 * @param object $context
3829 function get_related_contexts_string($context) {
3830 if ($parents = get_parent_contexts($context)) {
3831 return (' IN ('.$context->id
.','.implode(',', $parents).')');
3833 return (' ='.$context->id
);
3838 * Verifies if given capability installed.
3840 * @param string $capabilityname
3841 * @param bool $cached
3842 * @return book true if capability exists
3844 function is_valid_capability($capabilityname, $cached=true) {
3845 static $capsnames = null; // one request per page only
3847 if (is_null($capsnames) or !$cached) {
3848 $capsnames = get_records_menu('capabilities', '', '', '', 'name, 1');
3851 return array_key_exists($capabilityname, $capsnames);
3855 * Returns the human-readable, translated version of the capability.
3856 * Basically a big switch statement.
3857 * @param $capabilityname - e.g. mod/choice:readresponses
3859 function get_capability_string($capabilityname) {
3861 // Typical capabilityname is mod/choice:readresponses
3863 $names = split('/', $capabilityname);
3864 $stringname = $names[1]; // choice:readresponses
3865 $components = split(':', $stringname);
3866 $componentname = $components[0]; // choice
3868 switch ($names[0]) {
3870 $string = get_string($stringname, 'report_'.$componentname);
3874 $string = get_string($stringname, $componentname);
3878 $string = get_string($stringname, 'block_'.$componentname);
3882 if ($componentname == 'local') {
3883 $string = get_string($stringname, 'local');
3885 $string = get_string($stringname, 'role');
3890 $string = get_string($stringname, 'enrol_'.$componentname);
3894 $string = get_string($stringname, 'format_'.$componentname);
3898 $string = get_string($stringname, 'gradeexport_'.$componentname);
3902 $string = get_string($stringname, 'gradeimport_'.$componentname);
3906 $string = get_string($stringname, 'gradereport_'.$componentname);
3909 case 'coursereport':
3910 $string = get_string($stringname, 'coursereport_'.$componentname);
3914 $string = get_string($stringname);
3923 * This gets the mod/block/course/core etc strings.
3925 * @param $contextlevel
3927 function get_component_string($component, $contextlevel) {
3929 switch ($contextlevel) {
3931 case CONTEXT_SYSTEM
:
3932 if (preg_match('|^enrol/|', $component)) {
3933 $langname = str_replace('/', '_', $component);
3934 $string = get_string('enrolname', $langname);
3935 } else if (preg_match('|^block/|', $component)) {
3936 $langname = str_replace('/', '_', $component);
3937 $string = get_string('blockname', $langname);
3938 } else if (preg_match('|^local|', $component)) {
3939 $langname = str_replace('/', '_', $component);
3940 $string = get_string('local');
3941 } else if (preg_match('|^report/|', $component)) {
3942 $string = get_string('reports');
3944 $string = get_string('coresystem');
3949 $string = get_string('users');
3952 case CONTEXT_COURSECAT
:
3953 $string = get_string('categories');
3956 case CONTEXT_COURSE
:
3957 if (preg_match('|^gradeimport/|', $component)
3958 ||
preg_match('|^gradeexport/|', $component)
3959 ||
preg_match('|^gradereport/|', $component)) {
3960 $string = get_string('gradebook', 'admin');
3961 } else if (preg_match('|^coursereport/|', $component)) {
3962 $string = get_string('coursereports');
3964 $string = get_string('course');
3969 $string = get_string('group');
3972 case CONTEXT_MODULE
:
3973 $string = get_string('modulename', basename($component));
3977 if( $component == 'moodle' ){
3978 $string = get_string('block');
3980 $string = get_string('blockname', 'block_'.basename($component));
3985 error ('This is an unknown context $contextlevel (' . $contextlevel . ') in get_component_string!');
3993 * Gets the list of roles assigned to this context and up (parents)
3994 * @param object $context
3995 * @param view - set to true when roles are pulled for display only
3996 * this is so that we can filter roles with no visible
3997 * assignment, for example, you might want to "hide" all
3998 * course creators when browsing the course participants
4002 function get_roles_used_in_context($context, $view = false) {
4006 // filter for roles with all hidden assignments
4007 // no need to return when only pulling roles for reviewing
4008 // e.g. participants page.
4009 $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))?
' AND ra.hidden = 0 ':'';
4010 $contextlist = get_related_contexts_string($context);
4012 $sql = "SELECT DISTINCT r.id,
4016 FROM {$CFG->prefix}role_assignments ra,
4017 {$CFG->prefix}role r
4018 WHERE r.id = ra.roleid
4019 AND ra.contextid $contextlist
4021 ORDER BY r.sortorder ASC";
4023 return get_records_sql($sql);
4027 * This function is used to print roles column in user profile page.
4029 * @param object context
4032 function get_user_roles_in_context($userid, $context, $view=true){
4036 $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';
4037 $rolenames = array();
4038 if ($roles = get_records_sql($SQL)) {
4039 foreach ($roles as $userrole) {
4040 // MDL-12544, if we are in view mode and current user has no capability to view hidden assignment, skip it
4041 if ($userrole->hidden
&& $view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
4044 $rolenames[$userrole->roleid
] = $userrole->name
;
4047 $rolenames = role_fix_names($rolenames, $context); // Substitute aliases
4049 foreach ($rolenames as $roleid => $rolename) {
4050 $rolenames[$roleid] = '<a href="'.$CFG->wwwroot
.'/user/index.php?contextid='.$context->id
.'&roleid='.$roleid.'">'.$rolename.'</a>';
4052 $rolestring = implode(',', $rolenames);
4059 * Checks if a user can override capabilities of a particular role in this context
4060 * @param object $context
4061 * @param int targetroleid - the id of the role you want to override
4064 function user_can_override($context, $targetroleid) {
4066 // TODO: not needed anymore, remove in 2.0
4068 // first check if user has override capability
4069 // if not return false;
4070 if (!has_capability('moodle/role:override', $context)) {
4073 // pull out all active roles of this user from this context(or above)
4074 if ($userroles = get_user_roles($context)) {
4075 foreach ($userroles as $userrole) {
4076 // if any in the role_allow_override table, then it's ok
4077 if (get_record('role_allow_override', 'roleid', $userrole->roleid
, 'allowoverride', $targetroleid)) {
4088 * Checks if a user can assign users to a particular role in this context
4089 * @param object $context
4090 * @param int targetroleid - the id of the role you want to assign users to
4093 function user_can_assign($context, $targetroleid) {
4095 // first check if user has override capability
4096 // if not return false;
4097 if (!has_capability('moodle/role:assign', $context)) {
4100 // pull out all active roles of this user from this context(or above)
4101 if ($userroles = get_user_roles($context)) {
4102 foreach ($userroles as $userrole) {
4103 // if any in the role_allow_override table, then it's ok
4104 if (get_record('role_allow_assign', 'roleid', $userrole->roleid
, 'allowassign', $targetroleid)) {
4113 /** Returns all site roles in correct sort order.
4116 function get_all_roles() {
4117 return get_records('role', '', '', 'sortorder ASC');
4121 * gets all the user roles assigned in this context, or higher contexts
4122 * this is mainly used when checking if a user can assign a role, or overriding a role
4123 * i.e. we need to know what this user holds, in order to verify against allow_assign and
4124 * allow_override tables
4125 * @param object $context
4126 * @param int $userid
4127 * @param view - set to true when roles are pulled for display only
4128 * this is so that we can filter roles with no visible
4129 * assignment, for example, you might want to "hide" all
4130 * course creators when browsing the course participants
4134 function get_user_roles($context, $userid=0, $checkparentcontexts=true, $order='c.contextlevel DESC, r.sortorder ASC', $view=false) {
4136 global $USER, $CFG, $db;
4138 if (empty($userid)) {
4139 if (empty($USER->id
)) {
4142 $userid = $USER->id
;
4144 // set up hidden sql
4145 $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))?
' AND ra.hidden = 0 ':'';
4147 if ($checkparentcontexts && ($parents = get_parent_contexts($context))) {
4148 $contexts = ' ra.contextid IN ('.implode(',' , $parents).','.$context->id
.')';
4150 $contexts = ' ra.contextid = \''.$context->id
.'\'';
4153 if (!$return = get_records_sql('SELECT ra.*, r.name, r.shortname
4154 FROM '.$CFG->prefix
.'role_assignments ra,
4155 '.$CFG->prefix
.'role r,
4156 '.$CFG->prefix
.'context c
4157 WHERE ra.userid = '.$userid.'
4158 AND ra.roleid = r.id
4159 AND ra.contextid = c.id
4160 AND '.$contexts . $hiddensql .'
4161 ORDER BY '.$order)) {
4169 * Creates a record in the allow_override table
4170 * @param int sroleid - source roleid
4171 * @param int troleid - target roleid
4172 * @return int - id or false
4174 function allow_override($sroleid, $troleid) {
4175 $record = new object();
4176 $record->roleid
= $sroleid;
4177 $record->allowoverride
= $troleid;
4178 return insert_record('role_allow_override', $record);
4182 * Creates a record in the allow_assign table
4183 * @param int sroleid - source roleid
4184 * @param int troleid - target roleid
4185 * @return int - id or false
4187 function allow_assign($sroleid, $troleid) {
4188 $record = new object;
4189 $record->roleid
= $sroleid;
4190 $record->allowassign
= $troleid;
4191 return insert_record('role_allow_assign', $record);
4195 * Gets a list of roles that this user can assign in this context
4196 * @param object $context
4197 * @param string $field
4198 * @param int $rolenamedisplay
4201 function get_assignable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4204 if (!has_capability('moodle/role:assign', $context)) {
4208 $parents = get_parent_contexts($context);
4209 $parents[] = $context->id
;
4210 $contexts = implode(',' , $parents);
4212 if (!$roles = get_records_sql("SELECT ro.*
4213 FROM {$CFG->prefix}role ro,
4215 SELECT DISTINCT r.id
4216 FROM {$CFG->prefix}role r,
4217 {$CFG->prefix}role_assignments ra,
4218 {$CFG->prefix}role_allow_assign raa
4219 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4220 AND raa.roleid = ra.roleid AND r.id = raa.allowassign
4222 WHERE ro.id = inline_view.id
4223 ORDER BY ro.sortorder ASC")) {
4227 foreach ($roles as $role) {
4228 $roles[$role->id
] = $role->$field;
4231 return role_fix_names($roles, $context, $rolenamedisplay);
4235 * Gets a list of roles that this user can assign in this context, for the switchrole menu
4237 * @param object $context
4238 * @param string $field
4239 * @param int $rolenamedisplay
4242 function get_assignable_roles_for_switchrole($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4245 if (!has_capability('moodle/role:assign', $context)) {
4249 $parents = get_parent_contexts($context);
4250 $parents[] = $context->id
;
4251 $contexts = implode(',' , $parents);
4253 if (!$roles = get_records_sql("SELECT ro.*
4254 FROM {$CFG->prefix}role ro,
4256 SELECT DISTINCT r.id
4257 FROM {$CFG->prefix}role r,
4258 {$CFG->prefix}role_assignments ra,
4259 {$CFG->prefix}role_allow_assign raa,
4260 {$CFG->prefix}role_capabilities rc
4261 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4262 AND raa.roleid = ra.roleid AND r.id = raa.allowassign
4263 AND r.id = rc.roleid AND rc.capability = 'moodle/course:view' AND rc.capability != 'moodle/site:doanything'
4265 WHERE ro.id = inline_view.id
4266 ORDER BY ro.sortorder ASC")) {
4270 foreach ($roles as $role) {
4271 $roles[$role->id
] = $role->$field;
4274 return role_fix_names($roles, $context, $rolenamedisplay);
4278 * Gets a list of roles that this user can override or safeoverride in this context
4279 * @param object $context
4280 * @param string $field
4281 * @param int $rolenamedisplay
4284 function get_overridable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS
) {
4287 if (!has_capability('moodle/role:override', $context) and !has_capability('moodle/role:safeoverride', $context)) {
4291 $parents = get_parent_contexts($context);
4292 $parents[] = $context->id
;
4293 $contexts = implode(',' , $parents);
4295 if (!$roles = get_records_sql("SELECT ro.*
4296 FROM {$CFG->prefix}role ro,
4298 SELECT DISTINCT r.id
4299 FROM {$CFG->prefix}role r,
4300 {$CFG->prefix}role_assignments ra,
4301 {$CFG->prefix}role_allow_override rao
4302 WHERE ra.userid = $USER->id AND ra.contextid IN ($contexts)
4303 AND rao.roleid = ra.roleid AND r.id = rao.allowoverride
4305 WHERE ro.id = inline_view.id
4306 ORDER BY ro.sortorder ASC")) {
4310 foreach ($roles as $role) {
4311 $roles[$role->id
] = $role->$field;
4314 return role_fix_names($roles, $context, $rolenamedisplay);
4318 * Returns a role object that is the default role for new enrolments
4321 * @param object $course
4322 * @return object $role
4324 function get_default_course_role($course) {
4327 /// First let's take the default role the course may have
4328 if (!empty($course->defaultrole
)) {
4329 if ($role = get_record('role', 'id', $course->defaultrole
)) {
4334 /// Otherwise the site setting should tell us
4335 if ($CFG->defaultcourseroleid
) {
4336 if ($role = get_record('role', 'id', $CFG->defaultcourseroleid
)) {
4341 /// It's unlikely we'll get here, but just in case, try and find a student role
4342 if ($studentroles = get_roles_with_capability('moodle/legacy:student', CAP_ALLOW
)) {
4343 return array_shift($studentroles); /// Take the first one
4351 * Who has this capability in this context?
4353 * This can be a very expensive call - use sparingly and keep
4354 * the results if you are going to need them again soon.
4356 * Note if $fields is empty this function attempts to get u.*
4357 * which can get rather large - and has a serious perf impact
4360 * @param $context - object
4361 * @param $capability - string capability, or an array of capabilities, in which
4362 * case users having any of those capabilities will be returned.
4363 * For performance reasons, you are advised to put the capability
4364 * that the user is most likely to have first.
4365 * @param $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
4366 * @param $sort - the sort order. Default is lastaccess time.
4367 * @param $limitfrom - number of records to skip (offset)
4368 * @param $limitnum - number of records to fetch
4369 * @param $groups - single group or array of groups - only return
4370 * users who are in one of these group(s).
4371 * @param $exceptions - list of users to exclude
4372 * @param view - set to true when roles are pulled for display only
4373 * this is so that we can filter roles with no visible
4374 * assignment, for example, you might want to "hide" all
4375 * course creators when browsing the course participants
4377 * @param boolean $useviewallgroups if $groups is set the return users who
4378 * have capability both $capability and moodle/site:accessallgroups
4379 * in this context, as well as users who have $capability and who are
4382 function get_users_by_capability($context, $capability, $fields='', $sort='',
4383 $limitfrom='', $limitnum='', $groups='', $exceptions='', $doanything=true,
4384 $view=false, $useviewallgroups=false) {
4387 $ctxids = substr($context->path
, 1); // kill leading slash
4388 $ctxids = str_replace('/', ',', $ctxids);
4390 // Context is the frontpage
4391 $isfrontpage = false;
4392 $iscoursepage = false; // coursepage other than fp
4393 if ($context->contextlevel
== CONTEXT_COURSE
) {
4394 if ($context->instanceid
== SITEID
) {
4395 $isfrontpage = true;
4397 $iscoursepage = true;
4401 // What roles/rolecaps are interesting?
4402 if (is_array($capability)) {
4403 $caps = "'" . implode("','", $capability) . "'";
4404 $capabilities = $capability;
4406 $caps = "'" . $capability . "'";
4407 $capabilities = array($capability);
4409 if ($doanything===true) {
4410 $caps .= ",'moodle/site:doanything'";
4411 $capabilities[] = 'moodle/site:doanything';
4412 $doanything_join='';
4413 $doanything_cond='';
4415 // This is an outer join against
4416 // admin-ish roleids. Any row that succeeds
4417 // in JOINing here ends up removed from
4418 // the resultset. This means we remove
4419 // rolecaps from roles that also have
4420 // 'doanything' capabilities.
4421 $doanything_join="LEFT OUTER JOIN (
4422 SELECT DISTINCT rc.roleid
4423 FROM {$CFG->prefix}role_capabilities rc
4424 WHERE rc.capability='moodle/site:doanything'
4425 AND rc.permission=".CAP_ALLOW
."
4426 AND rc.contextid IN ($ctxids)
4428 ON rc.roleid=dar.roleid";
4429 $doanything_cond="AND dar.roleid IS NULL";
4432 // fetch all capability records - we'll walk several
4433 // times over them, and should be a small set
4435 $negperm = false; // has any negative (<0) permission?
4438 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability,
4439 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel
4440 FROM {$CFG->prefix}role_capabilities rc
4441 JOIN {$CFG->prefix}context ctx on rc.contextid = ctx.id
4443 WHERE rc.capability IN ($caps) AND ctx.id IN ($ctxids)
4445 ORDER BY rc.roleid ASC, ctx.depth ASC";
4446 if ($capdefs = get_records_sql($sql)) {
4447 foreach ($capdefs AS $rcid=>$rc) {
4448 $roleids[] = (int)$rc->roleid
;
4449 if ($rc->permission
< 0) {
4455 $roleids = array_unique($roleids);
4457 if (count($roleids)===0) { // noone here!
4461 // is the default role interesting? does it have
4462 // a relevant rolecap? (we use this a lot later)
4463 if (in_array((int)$CFG->defaultuserroleid
, $roleids, true)) {
4464 $defaultroleinteresting = true;
4466 $defaultroleinteresting = false;
4470 // Prepare query clauses
4472 $wherecond = array();
4474 // Non-deleted users. We never return deleted users.
4475 $wherecond['nondeleted'] = 'u.deleted = 0';
4479 if (is_array($groups)) {
4480 $grouptest = 'gm.groupid IN (' . implode(',', $groups) . ')';
4482 $grouptest = 'gm.groupid = ' . $groups;
4484 $grouptest = 'ra.userid IN (SELECT userid FROM ' .
4485 $CFG->prefix
. 'groups_members gm WHERE ' . $grouptest . ')';
4487 if ($useviewallgroups) {
4488 $viewallgroupsusers = get_users_by_capability($context,
4489 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
4490 $wherecond['groups'] = '('. $grouptest . ' OR ra.userid IN (' .
4491 implode(',', array_keys($viewallgroupsusers)) . '))';
4493 $wherecond['groups'] = '(' . $grouptest .')';
4498 if (!empty($exceptions)) {
4499 $wherecond['userexceptions'] = ' u.id NOT IN ('.$exceptions.')';
4502 /// Set up hidden role-assignments sql
4503 if ($view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
4504 $condhiddenra = 'AND ra.hidden = 0 ';
4505 $sscondhiddenra = 'AND ssra.hidden = 0 ';
4508 $sscondhiddenra = '';
4511 // Collect WHERE conditions
4512 $where = implode(' AND ', array_values($wherecond));
4514 $where = 'WHERE ' . $where;
4517 /// Set up default fields
4518 if (empty($fields)) {
4519 if ($iscoursepage) {
4520 $fields = 'u.*, ul.timeaccess as lastaccess';
4525 if (debugging('', DEBUG_DEVELOPER
) && strpos($fields, 'u.*') === false &&
4526 strpos($fields, 'u.id') === false) {
4527 debugging('u.id must be included in the list of fields passed to get_users_by_capability.', DEBUG_DEVELOPER
);
4531 /// Set up default sort
4532 if (empty($sort)) { // default to course lastaccess or just lastaccess
4533 if ($iscoursepage) {
4534 $sort = 'ul.timeaccess';
4536 $sort = 'u.lastaccess';
4539 $sortby = $sort ?
" ORDER BY $sort " : '';
4541 // User lastaccess JOIN
4542 if ((strpos($sort, 'ul.timeaccess') === FALSE) and (strpos($fields, 'ul.timeaccess') === FALSE)) { // user_lastaccess is not required MDL-13810
4545 $uljoin = "LEFT OUTER JOIN {$CFG->prefix}user_lastaccess ul
4546 ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
4550 // Simple cases - No negative permissions means we can take shortcuts
4554 // at the frontpage, and all site users have it - easy!
4555 if ($isfrontpage && !empty($CFG->defaultfrontpageroleid
)
4556 && in_array((int)$CFG->defaultfrontpageroleid
, $roleids, true)) {
4558 return get_records_sql("SELECT $fields
4559 FROM {$CFG->prefix}user u
4562 $limitfrom, $limitnum);
4565 // all site users have it, anyway
4566 // TODO: NOT ALWAYS! Check this case because this gets run for cases like this:
4567 // 1) Default role has the permission for a module thing like mod/choice:choose
4568 // 2) We are checking for an activity module context in a course
4569 // 3) Thus all users are returned even though course:view is also required
4570 if ($defaultroleinteresting) {
4571 $sql = "SELECT $fields
4572 FROM {$CFG->prefix}user u
4576 return get_records_sql($sql, $limitfrom, $limitnum);
4579 /// Simple SQL assuming no negative rolecaps.
4580 /// We use a subselect to grab the role assignments
4581 /// ensuring only one row per user -- even if they
4582 /// have many "relevant" role assignments.
4583 $select = " SELECT $fields";
4584 $from = " FROM {$CFG->prefix}user u
4585 JOIN (SELECT DISTINCT ssra.userid
4586 FROM {$CFG->prefix}role_assignments ssra
4587 WHERE ssra.contextid IN ($ctxids)
4588 AND ssra.roleid IN (".implode(',',$roleids) .")
4590 ) ra ON ra.userid = u.id
4592 return get_records_sql($select.$from.$where.$sortby, $limitfrom, $limitnum);
4596 // If there are any negative rolecaps, we need to
4597 // work through a subselect that will bring several rows
4598 // per user (one per RA).
4599 // Since we cannot do the job in pure SQL (not without SQL stored
4600 // procedures anyway), we end up tied to processing the data in PHP
4601 // all the way down to pagination.
4603 // In some cases, this will mean bringing across a ton of data --
4604 // when paginating, we have to walk the permisisons of all the rows
4605 // in the _previous_ pages to get the pagination correct in the case
4606 // of users that end up not having the permission - this removed.
4609 // Prepare the role permissions datastructure for fast lookups
4610 $roleperms = array(); // each role cap and depth
4611 foreach ($capdefs AS $rcid=>$rc) {
4613 $rid = (int)$rc->roleid
;
4614 $perm = (int)$rc->permission
;
4615 $rcdepth = (int)$rc->ctxdepth
;
4616 if (!isset($roleperms[$rc->capability
][$rid])) {
4617 $roleperms[$rc->capability
][$rid] = (object)array('perm' => $perm,
4618 'rcdepth' => $rcdepth);
4620 if ($roleperms[$rc->capability
][$rid]->perm
== CAP_PROHIBIT
) {
4623 // override - as we are going
4624 // from general to local perms
4625 // (as per the ORDER BY...depth ASC above)
4626 // and local perms win...
4627 $roleperms[$rc->capability
][$rid] = (object)array('perm' => $perm,
4628 'rcdepth' => $rcdepth);
4633 if ($context->contextlevel
== CONTEXT_SYSTEM
4635 ||
$defaultroleinteresting) {
4637 // Handle system / sitecourse / defaultrole-with-perhaps-neg-overrides
4638 // with a SELECT FROM user LEFT OUTER JOIN against ra -
4639 // This is expensive on the SQL and PHP sides -
4640 // moves a ton of data across the wire.
4641 $ss = "SELECT u.id as userid, ra.roleid,
4643 FROM {$CFG->prefix}user u
4644 LEFT OUTER JOIN {$CFG->prefix}role_assignments ra
4645 ON (ra.userid = u.id
4646 AND ra.contextid IN ($ctxids)
4647 AND ra.roleid IN (".implode(',',$roleids) .")
4649 LEFT OUTER JOIN {$CFG->prefix}context ctx
4650 ON ra.contextid=ctx.id
4653 // "Normal complex case" - the rolecaps we are after will
4654 // be defined in a role assignment somewhere.
4655 $ss = "SELECT ra.userid as userid, ra.roleid,
4657 FROM {$CFG->prefix}role_assignments ra
4658 JOIN {$CFG->prefix}context ctx
4659 ON ra.contextid=ctx.id
4660 WHERE ra.contextid IN ($ctxids)
4662 AND ra.roleid IN (".implode(',',$roleids) .")";
4665 $select = "SELECT $fields ,ra.roleid, ra.depth ";
4666 $from = "FROM ($ss) ra
4667 JOIN {$CFG->prefix}user u
4671 // Each user's entries MUST come clustered together
4672 // and RAs ordered in depth DESC - the role/cap resolution
4673 // code depends on this.
4674 $sort .= ' , ra.userid ASC, ra.depth DESC';
4675 $sortby .= ' , ra.userid ASC, ra.depth DESC ';
4677 $rs = get_recordset_sql($select.$from.$where.$sortby);
4680 // Process the user accounts+RAs, folding repeats together...
4682 // The processing for this recordset is tricky - to fold
4683 // the role/perms of users with multiple role-assignments
4684 // correctly while still processing one-row-at-a-time
4685 // we need to add a few additional 'private' fields to
4686 // the results array - so we can treat the rows as a
4687 // state machine to track the cap/perms and at what RA-depth
4688 // and RC-depth they were defined.
4690 // So what we do here is:
4691 // - loop over rows, checking pagination limits
4692 // - when we find a new user, if we are in the page add it to the
4693 // $results, and start building $ras array with its role-assignments
4694 // - when we are dealing with the next user, or are at the end of the userlist
4695 // (last rec or last in page), trigger the check-permission idiom
4696 // - the check permission idiom will
4697 // - add the default enrolment if needed
4698 // - call has_any_capability_from_rarc(), which based on RAs and RCs will return a bool
4699 // (should be fairly tight code ;-) )
4700 // - if the user has permission, all is good, just $c++ (counter)
4701 // - ...else, decrease the counter - so pagination is kept straight,
4702 // and (if we are in the page) remove from the results
4706 // pagination controls
4708 $limitfrom = (int)$limitfrom;
4709 $limitnum = (int)$limitnum;
4712 // Track our last user id so we know when we are dealing
4713 // with a new user...
4718 // $ras: role assignments, multidimensional array
4719 // treat as a stack - going from local to general
4720 // $ras = (( roleid=> x, $depth=>y) , ( roleid=> x, $depth=>y))
4722 while ($user = rs_fetch_next_record($rs)) {
4724 //error_log(" Record: " . print_r($user,1));
4727 // Pagination controls
4728 // Note that we might end up removing a user
4729 // that ends up _not_ having the rights,
4730 // therefore rolling back $c
4732 if ($lastuserid != $user->id
) {
4734 // Did the last user end up with a positive permission?
4735 if ($lastuserid !=0) {
4736 if ($defaultroleinteresting) {
4737 // add the role at the end of $ras
4738 $ras[] = array( 'roleid' => $CFG->defaultuserroleid
,
4741 if (has_any_capability_from_rarc($ras, $roleperms, $capabilities)) {
4744 // remove the user from the result set,
4745 // only if we are 'in the page'
4746 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4747 unset($results[$lastuserid]);
4752 // Did we hit pagination limit?
4753 if ($limitnum !==0 && $c >= ($limitfrom+
$limitnum)) { // we are done!
4757 // New user setup, and $ras reset
4758 $lastuserid = $user->id
;
4760 if (!empty($user->roleid
)) {
4761 $ras[] = array( 'roleid' => (int)$user->roleid
,
4762 'depth' => (int)$user->depth
);
4765 // if we are 'in the page', also add the rec
4766 // to the results...
4767 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4768 $results[$user->id
] = $user; // trivial
4771 // Additional RA for $lastuserid
4772 $ras[] = array( 'roleid'=>(int)$user->roleid
,
4773 'depth'=>(int)$user->depth
);
4776 } // end while(fetch)
4778 // Prune last entry if necessary
4779 if ($lastuserid !=0) {
4780 if ($defaultroleinteresting) {
4781 // add the role at the end of $ras
4782 $ras[] = array( 'roleid' => $CFG->defaultuserroleid
,
4785 if (!has_any_capability_from_rarc($ras, $roleperms, $capabilities)) {
4786 // remove the user from the result set,
4787 // only if we are 'in the page'
4788 if ($limitfrom === 0 ||
$c >= $limitfrom) {
4789 if (isset($results[$lastuserid])) {
4790 unset($results[$lastuserid]);
4800 * Fast (fast!) utility function to resolve if any of a list of capabilities is
4801 * granted, based on Role Assignments and Role Capabilities.
4803 * Used (at least) by get_users_by_capability().
4805 * If PHP had fast built-in memoize functions, we could
4806 * add a $contextid parameter and memoize the return values.
4808 * Note that this function must be kept in synch with has_capability_in_accessdata.
4810 * @param array $ras - role assignments
4811 * @param array $roleperms - role permissions
4812 * @param string $capabilities - array of capability names
4815 function has_any_capability_from_rarc($ras, $roleperms, $caps) {
4816 // Mini-state machine, using $hascap
4817 // $hascap[ 'moodle/foo:bar' ]->perm = CAP_SOMETHING (numeric constant)
4818 // $hascap[ 'moodle/foo:bar' ]->radepth = depth of the role assignment that set it
4819 // $hascap[ 'moodle/foo:bar' ]->rcdepth = depth of the rolecap that set it
4820 // -- when resolving conflicts, we need to look into radepth first, if unresolved
4825 // Compute which permission/roleassignment/rolecap
4826 // wins for each capability we are walking
4828 foreach ($ras as $ra) {
4829 foreach ($caps as $cap) {
4830 if (!isset($roleperms[$cap][$ra['roleid']])) {
4831 // nothing set for this cap - skip
4834 // We explicitly clone here as we
4835 // add more properties to it
4836 // that must stay separate from the
4837 // original roleperm data structure
4838 $rp = clone($roleperms[$cap][$ra['roleid']]);
4839 $rp->radepth
= $ra['depth'];
4841 // Trivial case, we are the first to set
4842 if (!isset($hascap[$cap])) {
4843 $hascap[$cap] = $rp;
4847 // Resolve who prevails, in order of precendence
4848 // - Prohibits always wins
4853 if ($rp->perm
=== CAP_PROHIBIT
) {
4854 $hascap[$cap] = $rp;
4857 if ($hascap[$cap]->perm
=== CAP_PROHIBIT
) {
4861 // Locality of RA - the look is ordered by depth DESC
4862 // so from local to general -
4863 // Higher RA loses to local RA... unless perm===0
4864 /// Thanks to the order of the records, $rp->radepth <= $hascap[$cap]->radepth
4865 if ($rp->radepth
> $hascap[$cap]->radepth
) {
4866 error_log('Should not happen @ ' . __FUNCTION__
.':'.__LINE__
);
4868 if ($rp->radepth
< $hascap[$cap]->radepth
) {
4869 if ($hascap[$cap]->perm
!==0) {
4870 // Wider RA loses to local RAs...
4873 // "Higher RA resolves conflict" case,
4874 // local RAs had cancelled eachother
4875 $hascap[$cap] = $rp;
4879 // Same ralevel - locality of RC wins
4880 if ($rp->rcdepth
> $hascap[$cap]->rcdepth
) {
4881 $hascap[$cap] = $rp;
4884 if ($rp->rcdepth
> $hascap[$cap]->rcdepth
) {
4887 // We match depth - add them
4888 $hascap[$cap]->perm +
= $rp->perm
;
4891 foreach ($caps as $capability) {
4892 if (isset($hascap[$capability]) && $hascap[$capability]->perm
> 0) {
4900 * Will re-sort a $users results array (from get_users_by_capability(), usually)
4901 * based on a sorting policy. This is to support the odd practice of
4902 * sorting teachers by 'authority', where authority was "lowest id of the role
4905 * Will execute 1 database query. Only suitable for small numbers of users, as it
4906 * uses an u.id IN() clause.
4908 * Notes about the sorting criteria.
4910 * As a default, we cannot rely on role.sortorder because then
4911 * admins/coursecreators will always win. That is why the sane
4912 * rule "is locality matters most", with sortorder as 2nd
4915 * If you want role.sortorder, use the 'sortorder' policy, and
4916 * name explicitly what roles you want to cover. It's probably
4917 * a good idea to see what roles have the capabilities you want
4918 * (array_diff() them against roiles that have 'can-do-anything'
4919 * to weed out admin-ish roles. Or fetch a list of roles from
4920 * variables like $CFG->coursemanagers .
4922 * @param array users Users' array, keyed on userid
4923 * @param object context
4924 * @param array roles - ids of the roles to include, optional
4925 * @param string policy - defaults to locality, more about
4926 * @return array - sorted copy of the array
4928 function sort_by_roleassignment_authority($users, $context, $roles=array(), $sortpolicy='locality') {
4931 $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
4932 $contextwhere = ' ra.contextid IN ('.str_replace('/', ',',substr($context->path
, 1)).')';
4933 if (empty($roles)) {
4936 $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
4939 $sql = "SELECT ra.userid
4940 FROM {$CFG->prefix}role_assignments ra
4941 JOIN {$CFG->prefix}role r
4943 JOIN {$CFG->prefix}context ctx
4944 ON ra.contextid=ctx.id
4951 // Default 'locality' policy -- read PHPDoc notes
4952 // about sort policies...
4953 $orderby = 'ORDER BY
4954 ctx.depth DESC, /* locality wins */
4955 r.sortorder ASC, /* rolesorting 2nd criteria */
4956 ra.id /* role assignment order tie-breaker */';
4957 if ($sortpolicy === 'sortorder') {
4958 $orderby = 'ORDER BY
4959 r.sortorder ASC, /* rolesorting 2nd criteria */
4960 ra.id /* role assignment order tie-breaker */';
4963 $sortedids = get_fieldset_sql($sql . $orderby);
4964 $sortedusers = array();
4967 foreach ($sortedids as $id) {
4969 if (isset($seen[$id])) {
4975 $sortedusers[$id] = $users[$id];
4977 return $sortedusers;
4981 * gets all the users assigned this role in this context or higher
4982 * @param int roleid (can also be an array of ints!)
4983 * @param int contextid
4984 * @param bool parent if true, get list of users assigned in higher context too
4985 * @param string fields - fields from user (u.) , role assignment (ra) or role (r.)
4986 * @param string sort - sort from user (u.) , role assignment (ra) or role (r.)
4987 * @param bool gethidden - whether to fetch hidden enrolments too
4990 function get_role_users($roleid, $context, $parent=false, $fields='', $sort='u.lastname ASC', $gethidden=true, $group='', $limitfrom='', $limitnum='') {
4993 if (empty($fields)) {
4994 $fields = 'u.id, u.confirmed, u.username, u.firstname, u.lastname, '.
4995 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.city, '.
4996 'u.country, u.picture, u.idnumber, u.department, u.institution, '.
4997 'u.emailstop, u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name as rolename';
5000 // whether this assignment is hidden
5001 $hiddensql = $gethidden ?
'': ' AND ra.hidden = 0 ';
5003 $parentcontexts = '';
5005 $parentcontexts = substr($context->path
, 1); // kill leading slash
5006 $parentcontexts = str_replace('/', ',', $parentcontexts);
5007 if ($parentcontexts !== '') {
5008 $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
5012 if (is_array($roleid)) {
5013 $roleselect = ' AND ra.roleid IN (' . implode(',',$roleid) .')';
5014 } elseif (!empty($roleid)) { // should not test for int, because it can come in as a string
5015 $roleselect = "AND ra.roleid = $roleid";
5021 $groupjoin = "JOIN {$CFG->prefix}groups_members gm
5022 ON gm.userid = u.id";
5023 $groupselect = " AND gm.groupid = $group ";
5029 $SQL = "SELECT $fields, ra.roleid
5030 FROM {$CFG->prefix}role_assignments ra
5031 JOIN {$CFG->prefix}user u
5033 JOIN {$CFG->prefix}role r
5036 WHERE (ra.contextid = $context->id $parentcontexts)
5041 "; // join now so that we can just use fullname() later
5043 return get_records_sql($SQL, $limitfrom, $limitnum);
5047 * Counts all the users assigned this role in this context or higher
5048 * @param int roleid (can also be an array of ints!)
5049 * @param int contextid
5050 * @param bool parent if true, get list of users assigned in higher context too
5053 function count_role_users($roleid, $context, $parent=false) {
5057 if ($contexts = get_parent_contexts($context)) {
5058 $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
5060 $parentcontexts = '';
5063 $parentcontexts = '';
5067 if (is_numeric($roleid)) {
5068 $rolesql = "AND r.roleid = $roleid";
5070 else if (is_array($roleid)) {
5071 $rolesql = "AND r.roleid IN (" . implode(',', $roleid) . ")";
5074 $SQL = "SELECT count(u.id)
5075 FROM {$CFG->prefix}role_assignments r
5076 JOIN {$CFG->prefix}user u
5078 WHERE (r.contextid = $context->id $parentcontexts)
5082 return count_records_sql($SQL);
5086 * This function gets the list of courses that this user has a particular capability in.
5087 * It is still not very efficient.
5088 * @param string $capability Capability in question
5089 * @param int $userid User ID or null for current user
5090 * @param bool $doanything True if 'doanything' is permitted (default)
5091 * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
5092 * otherwise use a comma-separated list of the fields you require, not including id
5093 * @param string $orderby If set, use a comma-separated list of fields from course
5094 * table with sql modifiers (DESC) if needed
5095 * @return array Array of courses, may have zero entries. Or false if query failed.
5097 function get_user_capability_course($capability, $userid=NULL,$doanything=true,$fieldsexceptid='',$orderby='') {
5098 // Convert fields list and ordering
5100 if($fieldsexceptid) {
5101 $fields=explode(',',$fieldsexceptid);
5102 foreach($fields as $field) {
5103 $fieldlist.=',c.'.$field;
5107 $fields=explode(',',$orderby);
5109 foreach($fields as $field) {
5113 $orderby.='c.'.$field;
5115 $orderby='ORDER BY '.$orderby;
5118 // Obtain a list of everything relevant about all courses including context.
5119 // Note the result can be used directly as a context (we are going to), the course
5120 // fields are just appended.
5122 $rs=get_recordset_sql("
5124 x.*,c.id AS courseid$fieldlist
5126 {$CFG->prefix}course c
5127 INNER JOIN {$CFG->prefix}context x ON c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE
."
5134 // Check capability for each course in turn
5136 while($coursecontext=rs_fetch_next_record($rs)) {
5137 if(has_capability($capability,$coursecontext,$userid,$doanything)) {
5138 // We've got the capability. Make the record look like a course record
5140 $coursecontext->id
=$coursecontext->courseid
;
5141 unset($coursecontext->courseid
);
5142 unset($coursecontext->contextlevel
);
5143 unset($coursecontext->instanceid
);
5144 $courses[]=$coursecontext;
5150 /** This function finds the roles assigned directly to this context only
5151 * i.e. no parents role
5152 * @param object $context
5155 function get_roles_on_exact_context($context) {
5159 return get_records_sql("SELECT r.*
5160 FROM {$CFG->prefix}role_assignments ra,
5161 {$CFG->prefix}role r
5162 WHERE ra.roleid = r.id
5163 AND ra.contextid = $context->id");
5168 * Switches the current user to another role for the current session and only
5169 * in the given context.
5171 * The caller *must* check
5172 * - that this op is allowed
5173 * - that the requested role can be assigned in this ctx
5174 * (hint, use get_assignable_roles_for_switchrole())
5175 * - that the requested role is NOT $CFG->defaultuserroleid
5177 * To "unswitch" pass 0 as the roleid.
5179 * This function *will* modify $USER->access - beware
5181 * @param integer $roleid
5182 * @param object $context
5185 function role_switch($roleid, $context) {
5191 // - Add the ghost RA to $USER->access
5192 // as $USER->access['rsw'][$path] = $roleid
5194 // - Make sure $USER->access['rdef'] has the roledefs
5195 // it needs to honour the switcheroo
5197 // Roledefs will get loaded "deep" here - down to the last child
5198 // context. Note that
5200 // - When visiting subcontexts, our selective accessdata loading
5201 // will still work fine - though those ra/rdefs will be ignored
5202 // appropriately while the switch is in place
5204 // - If a switcheroo happens at a category with tons of courses
5205 // (that have many overrides for switched-to role), the session
5206 // will get... quite large. Sometimes you just can't win.
5208 // To un-switch just unset($USER->access['rsw'][$path])
5211 // Add the switch RA
5212 if (!isset($USER->access
['rsw'])) {
5213 $USER->access
['rsw'] = array();
5217 unset($USER->access
['rsw'][$context->path
]);
5218 if (empty($USER->access
['rsw'])) {
5219 unset($USER->access
['rsw']);
5224 $USER->access
['rsw'][$context->path
]=$roleid;
5227 $USER->access
= get_role_access_bycontext($roleid, $context,
5230 /* DO WE NEED THIS AT ALL???
5231 // Add some permissions we are really going
5232 // to always need, even if the role doesn't have them!
5234 $USER->capabilities[$context->id]['moodle/course:view'] = CAP_ALLOW;
5241 // get any role that has an override on exact context
5242 function get_roles_with_override_on_context($context) {
5246 return get_records_sql("SELECT r.*
5247 FROM {$CFG->prefix}role_capabilities rc,
5248 {$CFG->prefix}role r
5249 WHERE rc.roleid = r.id
5250 AND rc.contextid = $context->id");
5253 // get all capabilities for this role on this context (overrids)
5254 function get_capabilities_from_role_on_context($role, $context) {
5258 return get_records_sql("SELECT *
5259 FROM {$CFG->prefix}role_capabilities
5260 WHERE contextid = $context->id
5261 AND roleid = $role->id");
5264 // find out which roles has assignment on this context
5265 function get_roles_with_assignment_on_context($context) {
5269 return get_records_sql("SELECT r.*
5270 FROM {$CFG->prefix}role_assignments ra,
5271 {$CFG->prefix}role r
5272 WHERE ra.roleid = r.id
5273 AND ra.contextid = $context->id");
5279 * Find all user assignemnt of users for this role, on this context
5281 function get_users_from_role_on_context($role, $context) {
5285 return get_records_sql("SELECT *
5286 FROM {$CFG->prefix}role_assignments
5287 WHERE contextid = $context->id
5288 AND roleid = $role->id");
5292 * Simple function returning a boolean true if roles exist, otherwise false
5294 function user_has_role_assignment($userid, $roleid, $contextid=0) {
5297 return record_exists('role_assignments', 'userid', $userid, 'roleid', $roleid, 'contextid', $contextid);
5299 return record_exists('role_assignments', 'userid', $userid, 'roleid', $roleid);
5304 * Get role name or alias if exists and format the text.
5305 * @param object $role role object
5306 * @param object $coursecontext
5307 * @return $string name of role in course context
5309 function role_get_name($role, $coursecontext) {
5310 if ($r = get_record('role_names','roleid', $role->id
,'contextid', $coursecontext->id
)) {
5311 return strip_tags(format_string($r->name
));
5313 return strip_tags(format_string($role->name
));
5318 * Prepare list of roles for display, apply aliases and format text
5319 * @param array $roleoptions array roleid=>rolename
5320 * @param object $context
5321 * @return array of role names
5323 function role_fix_names($roleoptions, $context, $rolenamedisplay=ROLENAME_ALIAS
) {
5324 if ($rolenamedisplay != ROLENAME_ORIGINAL
&& !empty($context->id
)) {
5325 if ($context->contextlevel
== CONTEXT_MODULE ||
$context->contextlevel
== CONTEXT_BLOCK
) { // find the parent course context
5326 if ($parentcontextid = array_shift(get_parent_contexts($context))) {
5327 $context = get_context_instance_by_id($parentcontextid);
5330 if ($aliasnames = get_records('role_names', 'contextid', $context->id
)) {
5331 if ($rolenamedisplay == ROLENAME_ALIAS
) {
5332 foreach ($aliasnames as $alias) {
5333 if (isset($roleoptions[$alias->roleid
])) {
5334 $roleoptions[$alias->roleid
] = format_string($alias->name
);
5337 } else if ($rolenamedisplay == ROLENAME_BOTH
) {
5338 foreach ($aliasnames as $alias) {
5339 if (isset($roleoptions[$alias->roleid
])) {
5340 $roleoptions[$alias->roleid
] = format_string($alias->name
).' ('.format_string($roleoptions[$alias->roleid
]).')';
5346 foreach ($roleoptions as $rid => $name) {
5347 $roleoptions[$rid] = strip_tags(format_string($name));
5349 return $roleoptions;
5353 * This function helps admin/roles/manage.php etc to detect if a new line should be printed
5354 * when we read in a new capability
5355 * most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
5356 * but when we are in grade, all reports/import/export capabilites should be together
5357 * @param string a - component string a
5358 * @param string b - component string b
5359 * @return bool - whether 2 component are in different "sections"
5361 function component_level_changed($cap, $comp, $contextlevel) {
5363 if ($cap->component
== 'enrol/authorize' && $comp =='enrol/authorize') {
5367 if (strstr($cap->component
, '/') && strstr($comp, '/')) {
5368 $compsa = explode('/', $cap->component
);
5369 $compsb = explode('/', $comp);
5371 // list of system reports
5372 if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
5376 // we are in gradebook, still
5377 if (($compsa[0] == 'gradeexport' ||
$compsa[0] == 'gradeimport' ||
$compsa[0] == 'gradereport') &&
5378 ($compsb[0] == 'gradeexport' ||
$compsb[0] == 'gradeimport' ||
$compsb[0] == 'gradereport')) {
5382 if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
5387 return ($cap->component
!= $comp ||
$cap->contextlevel
!= $contextlevel);
5391 * Populate context.path and context.depth where missing.
5392 * @param bool $force force a complete rebuild of the path and depth fields.
5393 * @param bool $feedback display feedback (during upgrade usually)
5396 function build_context_path($force=false, $feedback=false) {
5398 require_once($CFG->libdir
.'/ddllib.php');
5401 $sitectx = get_system_context(!$force);
5402 $base = '/'.$sitectx->id
;
5405 $sitecoursectx = get_record('context',
5406 'contextlevel', CONTEXT_COURSE
,
5407 'instanceid', SITEID
);
5408 if ($force ||
$sitecoursectx->path
!== "$base/{$sitecoursectx->id}") {
5409 set_field('context', 'path', "$base/{$sitecoursectx->id}",
5410 'id', $sitecoursectx->id
);
5411 set_field('context', 'depth', 2,
5412 'id', $sitecoursectx->id
);
5413 $sitecoursectx = get_record('context',
5414 'contextlevel', CONTEXT_COURSE
,
5415 'instanceid', SITEID
);
5418 $ctxemptyclause = " AND (ctx.path IS NULL
5420 $emptyclause = " AND ({$CFG->prefix}context.path IS NULL
5421 OR {$CFG->prefix}context.depth=0) ";
5423 $ctxemptyclause = $emptyclause = '';
5427 * - mysql does not allow to use FROM in UPDATE statements
5428 * - using two tables after UPDATE works in mysql, but might give unexpected
5429 * results in pg 8 (depends on configuration)
5430 * - using table alias in UPDATE does not work in pg < 8.2
5432 if ($CFG->dbfamily
== 'mysql') {
5433 $updatesql = "UPDATE {$CFG->prefix}context ct, {$CFG->prefix}context_temp temp
5434 SET ct.path = temp.path,
5435 ct.depth = temp.depth
5436 WHERE ct.id = temp.id";
5437 } else if ($CFG->dbfamily
== 'oracle') {
5438 $updatesql = "UPDATE {$CFG->prefix}context ct
5439 SET (ct.path, ct.depth) =
5440 (SELECT temp.path, temp.depth
5441 FROM {$CFG->prefix}context_temp temp
5442 WHERE temp.id=ct.id)
5443 WHERE EXISTS (SELECT 'x'
5444 FROM {$CFG->prefix}context_temp temp
5445 WHERE temp.id = ct.id)";
5447 $updatesql = "UPDATE {$CFG->prefix}context
5448 SET path = temp.path,
5450 FROM {$CFG->prefix}context_temp temp
5451 WHERE temp.id={$CFG->prefix}context.id";
5454 $udelsql = "TRUNCATE TABLE {$CFG->prefix}context_temp";
5456 // Top level categories
5457 $sql = "UPDATE {$CFG->prefix}context
5458 SET depth=2, path=" . sql_concat("'$base/'", 'id') . "
5459 WHERE contextlevel=".CONTEXT_COURSECAT
."
5460 AND EXISTS (SELECT 'x'
5461 FROM {$CFG->prefix}course_categories cc
5462 WHERE cc.id = {$CFG->prefix}context.instanceid
5466 execute_sql($sql, $feedback);
5468 execute_sql($udelsql, $feedback);
5470 // Deeper categories - one query per depthlevel
5471 $maxdepth = get_field_sql("SELECT MAX(depth)
5472 FROM {$CFG->prefix}course_categories");
5473 for ($n=2;$n<=$maxdepth;$n++
) {
5474 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5475 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", $n+1
5476 FROM {$CFG->prefix}context ctx
5477 JOIN {$CFG->prefix}course_categories c ON ctx.instanceid=c.id
5478 JOIN {$CFG->prefix}context pctx ON c.parent=pctx.instanceid
5479 WHERE ctx.contextlevel=".CONTEXT_COURSECAT
."
5480 AND pctx.contextlevel=".CONTEXT_COURSECAT
."
5482 AND NOT EXISTS (SELECT 'x'
5483 FROM {$CFG->prefix}context_temp temp
5484 WHERE temp.id = ctx.id)
5486 execute_sql($sql, $feedback);
5488 // this is needed after every loop
5490 execute_sql($updatesql, $feedback);
5491 execute_sql($udelsql, $feedback);
5494 // Courses -- except sitecourse
5495 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5496 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5497 FROM {$CFG->prefix}context ctx
5498 JOIN {$CFG->prefix}course c ON ctx.instanceid=c.id
5499 JOIN {$CFG->prefix}context pctx ON c.category=pctx.instanceid
5500 WHERE ctx.contextlevel=".CONTEXT_COURSE
."
5501 AND c.id!=".SITEID
."
5502 AND pctx.contextlevel=".CONTEXT_COURSECAT
."
5503 AND NOT EXISTS (SELECT 'x'
5504 FROM {$CFG->prefix}context_temp temp
5505 WHERE temp.id = ctx.id)
5507 execute_sql($sql, $feedback);
5509 execute_sql($updatesql, $feedback);
5510 execute_sql($udelsql, $feedback);
5513 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5514 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5515 FROM {$CFG->prefix}context ctx
5516 JOIN {$CFG->prefix}course_modules cm ON ctx.instanceid=cm.id
5517 JOIN {$CFG->prefix}context pctx ON cm.course=pctx.instanceid
5518 WHERE ctx.contextlevel=".CONTEXT_MODULE
."
5519 AND pctx.contextlevel=".CONTEXT_COURSE
."
5520 AND NOT EXISTS (SELECT 'x'
5521 FROM {$CFG->prefix}context_temp temp
5522 WHERE temp.id = ctx.id)
5524 execute_sql($sql, $feedback);
5526 execute_sql($updatesql, $feedback);
5527 execute_sql($udelsql, $feedback);
5529 // Blocks - non-pinned course-view only
5530 $sql = "INSERT INTO {$CFG->prefix}context_temp (id, path, depth)
5531 SELECT ctx.id, ".sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
5532 FROM {$CFG->prefix}context ctx
5533 JOIN {$CFG->prefix}block_instance bi ON ctx.instanceid = bi.id
5534 JOIN {$CFG->prefix}context pctx ON bi.pageid=pctx.instanceid
5535 WHERE ctx.contextlevel=".CONTEXT_BLOCK
."
5536 AND pctx.contextlevel=".CONTEXT_COURSE
."
5537 AND bi.pagetype='course-view'
5538 AND NOT EXISTS (SELECT 'x'
5539 FROM {$CFG->prefix}context_temp temp
5540 WHERE temp.id = ctx.id)
5542 execute_sql($sql, $feedback);
5544 execute_sql($updatesql, $feedback);
5545 execute_sql($udelsql, $feedback);
5548 $sql = "UPDATE {$CFG->prefix}context
5549 SET depth=2, path=".sql_concat("'$base/'", 'id')."
5550 WHERE contextlevel=".CONTEXT_BLOCK
."
5551 AND EXISTS (SELECT 'x'
5552 FROM {$CFG->prefix}block_instance bi
5553 WHERE bi.id = {$CFG->prefix}context.instanceid
5554 AND bi.pagetype!='course-view')
5556 execute_sql($sql, $feedback);
5559 $sql = "UPDATE {$CFG->prefix}context
5560 SET depth=2, path=".sql_concat("'$base/'", 'id')."
5561 WHERE contextlevel=".CONTEXT_USER
."
5562 AND EXISTS (SELECT 'x'
5563 FROM {$CFG->prefix}user u
5564 WHERE u.id = {$CFG->prefix}context.instanceid)
5566 execute_sql($sql, $feedback);
5570 //TODO: fix group contexts
5572 // reset static course cache - it might have incorrect cached data
5573 global $context_cache, $context_cache_id;
5574 $context_cache = array();
5575 $context_cache_id = array();
5580 * Update the path field of the context and
5581 * all the dependent subcontexts that follow
5584 * The most important thing here is to be as
5585 * DB efficient as possible. This op can have a
5586 * massive impact in the DB.
5588 * @param obj current context obj
5589 * @param obj newparent new parent obj
5592 function context_moved($context, $newparent) {
5595 $frompath = $context->path
;
5596 $newpath = $newparent->path
. '/' . $context->id
;
5599 if (($newparent->depth +
1) != $context->depth
) {
5600 $setdepth = ", depth= depth + ({$newparent->depth} - {$context->depth}) + 1";
5602 $sql = "UPDATE {$CFG->prefix}context
5605 WHERE path='$frompath'";
5606 execute_sql($sql,false);
5608 $len = strlen($frompath);
5609 /// MDL-16655 - Substring MSSQL function *requires* 3rd parameter
5610 $substr3rdparam = '';
5611 if ($CFG->dbfamily
== 'mssql') {
5612 $substr3rdparam = ', len(path)';
5614 $sql = "UPDATE {$CFG->prefix}context
5615 SET path = ".sql_concat("'$newpath'", sql_substr() .'(path, '.$len.' +1'.$substr3rdparam.')')."
5617 WHERE path LIKE '{$frompath}/%'";
5618 execute_sql($sql,false);
5620 mark_context_dirty($frompath);
5621 mark_context_dirty($newpath);
5626 * Turn the ctx* fields in an objectlike record
5627 * into a context subobject. This allows
5628 * us to SELECT from major tables JOINing with
5629 * context at no cost, saving a ton of context
5632 function make_context_subobj($rec) {
5633 $ctx = new StdClass
;
5634 $ctx->id
= $rec->ctxid
; unset($rec->ctxid
);
5635 $ctx->path
= $rec->ctxpath
; unset($rec->ctxpath
);
5636 $ctx->depth
= $rec->ctxdepth
; unset($rec->ctxdepth
);
5637 $ctx->contextlevel
= $rec->ctxlevel
; unset($rec->ctxlevel
);
5638 $ctx->instanceid
= $rec->id
;
5640 $rec->context
= $ctx;
5645 * Do some basic, quick checks to see whether $rec->context looks like a
5646 * valid context object.
5648 * @param object $rec a think that has a context, for example a course,
5649 * course category, course modules, etc.
5650 * @param integer $contextlevel the type of thing $rec is, one of the CONTEXT_... constants.
5651 * @return boolean whether $rec->context looks like the correct context object
5654 function is_context_subobj_valid($rec, $contextlevel) {
5655 return isset($rec->context
) && isset($rec->context
->id
) &&
5656 isset($rec->context
->path
) && isset($rec->context
->depth
) &&
5657 isset($rec->context
->contextlevel
) && isset($rec->context
->instanceid
) &&
5658 $rec->context
->contextlevel
== $contextlevel && $rec->context
->instanceid
== $rec->id
;
5662 * When you have a record (for example a $category, $course, $user or $cm that may,
5663 * or may not, have come from a place that does make_context_subobj, you can use
5664 * this method to ensure that $rec->context is present and correct before you continue.
5666 * @param object $rec a thing that has an associated context.
5667 * @param integer $contextlevel the type of thing $rec is, one of the CONTEXT_... constants.
5669 function ensure_context_subobj_present(&$rec, $contextlevel) {
5670 if (!is_context_subobj_valid($rec, $contextlevel)) {
5671 $rec->context
= get_context_instance($contextlevel, $rec->id
);
5676 * Fetch recent dirty contexts to know cheaply whether our $USER->access
5677 * is stale and needs to be reloaded.
5681 * @return array of dirty contexts
5683 function get_dirty_contexts($time) {
5684 return get_cache_flags('accesslib/dirtycontexts', $time-2);
5688 * Mark a context as dirty (with timestamp)
5689 * so as to force reloading of the context.
5690 * @param string $path context path
5692 function mark_context_dirty($path) {
5693 global $CFG, $DIRTYCONTEXTS;
5694 // only if it is a non-empty string
5695 if (is_string($path) && $path !== '') {
5696 set_cache_flag('accesslib/dirtycontexts', $path, 1, time()+
$CFG->sessiontimeout
);
5697 if (isset($DIRTYCONTEXTS)) {
5698 $DIRTYCONTEXTS[$path] = 1;
5704 * Will walk the contextpath to answer whether
5705 * the contextpath is dirty
5707 * @param array $contexts array of strings
5708 * @param obj/array dirty contexts from get_dirty_contexts()
5711 function is_contextpath_dirty($pathcontexts, $dirty) {
5713 foreach ($pathcontexts as $ctx) {
5714 $path = $path.'/'.$ctx;
5715 if (isset($dirty[$path])) {
5724 * switch role order (used in admin/roles/manage.php)
5726 * @param int $first id of role to move down
5727 * @param int $second id of role to move up
5729 * @return bool success or failure
5731 function switch_roles($first, $second) {
5733 //first find temorary sortorder number
5734 $tempsort = count_records('role') +
3;
5735 while (get_record('role','sortorder', $tempsort)) {
5740 $r1->id
= $first->id
;
5741 $r1->sortorder
= $tempsort;
5743 $r2->id
= $second->id
;
5744 $r2->sortorder
= $first->sortorder
;
5746 if (!update_record('role', $r1)) {
5747 debugging("Can not update role with ID $r1->id!");
5751 if (!update_record('role', $r2)) {
5752 debugging("Can not update role with ID $r2->id!");
5756 $r1->sortorder
= $second->sortorder
;
5757 if (!update_record('role', $r1)) {
5758 debugging("Can not update role with ID $r1->id!");
5766 * duplicates all the base definitions of a role
5768 * @param object $sourcerole role to copy from
5769 * @param int $targetrole id of role to copy to
5773 function role_cap_duplicate($sourcerole, $targetrole) {
5775 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
5776 $caps = get_records_sql("SELECT * FROM {$CFG->prefix}role_capabilities
5777 WHERE roleid = $sourcerole->id
5778 AND contextid = $systemcontext->id");
5779 // adding capabilities
5780 foreach ($caps as $cap) {
5782 $cap->roleid
= $targetrole;
5783 insert_record('role_capabilities', $cap);