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