cad7e53f1808e0af020c0e7814f483d664592cfd
[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     allow_override($editteacherrole, $noneditteacherrole);
1770     allow_override($editteacherrole, $studentrole);
1771     allow_override($editteacherrole, $guestrole);
1775 /**
1776  * Returns array of all legacy roles.
1777  */
1778 function get_legacy_roles() {
1779     return array(
1780         'admin'          => 'moodle/legacy:admin',
1781         'coursecreator'  => 'moodle/legacy:coursecreator',
1782         'editingteacher' => 'moodle/legacy:editingteacher',
1783         'teacher'        => 'moodle/legacy:teacher',
1784         'student'        => 'moodle/legacy:student',
1785         'guest'          => 'moodle/legacy:guest',
1786         'user'           => 'moodle/legacy:user'
1787     );
1790 function get_legacy_type($roleid) {
1791     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1792     $legacyroles = get_legacy_roles();
1794     $result = '';
1795     foreach($legacyroles as $ltype=>$lcap) {
1796         $localoverride = get_local_override($roleid, $sitecontext->id, $lcap);
1797         if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) {
1798             //choose first selected legacy capability - reset the rest
1799             if (empty($result)) {
1800                 $result = $ltype;
1801             } else {
1802                 unassign_capability($lcap, $roleid);
1803             }
1804         }
1805     }
1807     return $result;
1810 /**
1811  * Assign the defaults found in this capabality definition to roles that have
1812  * the corresponding legacy capabilities assigned to them.
1813  * @param $legacyperms - an array in the format (example):
1814  *                      'guest' => CAP_PREVENT,
1815  *                      'student' => CAP_ALLOW,
1816  *                      'teacher' => CAP_ALLOW,
1817  *                      'editingteacher' => CAP_ALLOW,
1818  *                      'coursecreator' => CAP_ALLOW,
1819  *                      'admin' => CAP_ALLOW
1820  * @return boolean - success or failure.
1821  */
1822 function assign_legacy_capabilities($capability, $legacyperms) {
1824     $legacyroles = get_legacy_roles();
1826     foreach ($legacyperms as $type => $perm) {
1828         $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1830         if (!array_key_exists($type, $legacyroles)) {
1831             print_error('invalidlegacy', '', '', $type);
1832         }
1834         if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW)) {
1835             foreach ($roles as $role) {
1836                 // Assign a site level capability.
1837                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1838                     return false;
1839                 }
1840             }
1841         }
1842     }
1843     return true;
1847 /**
1848  * Checks to see if a capability is a legacy capability.
1849  * @param $capabilityname
1850  * @return boolean
1851  */
1852 function islegacy($capabilityname) {
1853     if (strpos($capabilityname, 'moodle/legacy') === 0) {
1854         return true;
1855     } else {
1856         return false;
1857     }
1862 /**********************************
1863  * Context Manipulation functions *
1864  **********************************/
1866 /**
1867  * Create a new context record for use by all roles-related stuff
1868  * assumes that the caller has done the homework.
1869  *
1870  * @param $level
1871  * @param $instanceid
1872  *
1873  * @return object newly created context
1874  */
1875 function create_context($contextlevel, $instanceid) {
1877     global $CFG, $DB;
1879     if ($contextlevel == CONTEXT_SYSTEM) {
1880         return create_system_context();
1881     }
1883     $context = new object();
1884     $context->contextlevel = $contextlevel;
1885     $context->instanceid = $instanceid;
1887     // Define $context->path based on the parent
1888     // context. In other words... Who is your daddy?
1889     $basepath  = '/' . SYSCONTEXTID;
1890     $basedepth = 1;
1892     $result = true;
1894     switch ($contextlevel) {
1895         case CONTEXT_COURSECAT:
1896             $sql = "SELECT ctx.path, ctx.depth
1897                       FROM {context}           ctx
1898                       JOIN {course_categories} cc
1899                            ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1900                      WHERE cc.id=?";
1901             $params = array($instanceid);
1902             if ($p = $DB->get_record_sql($sql, $params)) {
1903                 $basepath  = $p->path;
1904                 $basedepth = $p->depth;
1905             } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid))) {
1906                 if (empty($category->parent)) {
1907                     // ok - this is a top category
1908                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
1909                     $basepath  = $parent->path;
1910                     $basedepth = $parent->depth;
1911                 } else {
1912                     // wrong parent category - no big deal, this can be fixed later
1913                     $basepath  = null;
1914                     $basedepth = 0;
1915                 }
1916             } else {
1917                 // incorrect category id
1918                 $result = false;
1919             }
1920             break;
1922         case CONTEXT_COURSE:
1923             $sql = "SELECT ctx.path, ctx.depth
1924                       FROM {context} ctx
1925                       JOIN {course}  c
1926                            ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1927                      WHERE c.id=? AND c.id !=" . SITEID;
1928             $params = array($instanceid);
1929             if ($p = $DB->get_record_sql($sql, $params)) {
1930                 $basepath  = $p->path;
1931                 $basedepth = $p->depth;
1932             } else if ($course = $DB->get_record('course', array('id'=>$instanceid))) {
1933                 if ($course->id == SITEID) {
1934                     //ok - no parent category
1935                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
1936                     $basepath  = $parent->path;
1937                     $basedepth = $parent->depth;
1938                 } else {
1939                     // wrong parent category of course - no big deal, this can be fixed later
1940                     $basepath  = null;
1941                     $basedepth = 0;
1942                 }
1943             } else if ($instanceid == SITEID) {
1944                 // no errors for missing site course during installation
1945                 return false;
1946             } else {
1947                 // incorrect course id
1948                 $result = false;
1949             }
1950             break;
1952         case CONTEXT_MODULE:
1953             $sql = "SELECT ctx.path, ctx.depth
1954                       FROM {context}        ctx
1955                       JOIN {course_modules} cm
1956                            ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1957                      WHERE cm.id=?";
1958             $params = array($instanceid);
1959             if ($p = $DB->get_record_sql($sql, $params)) {
1960                 $basepath  = $p->path;
1961                 $basedepth = $p->depth;
1962             } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid))) {
1963                 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course)) {
1964                     $basepath  = $parent->path;
1965                     $basedepth = $parent->depth;
1966                 } else {
1967                     // course does not exist - modules can not exist without a course
1968                     $result = false;
1969                 }
1970             } else {
1971                 // cm does not exist
1972                 $result = false;
1973             }
1974             break;
1976         case CONTEXT_BLOCK:
1977             // Only non-pinned & course-page based
1978             $sql = "SELECT ctx.path, ctx.depth
1979                       FROM {context}        ctx
1980                       JOIN {block_instance} bi
1981                            ON (bi.pageid=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1982                      WHERE bi.id=? AND bi.pagetype='course-view'";
1983             $params = array($instanceid);
1984             if ($p = $DB->get_record_sql($sql, $params)) {
1985                 $basepath  = $p->path;
1986                 $basedepth = $p->depth;
1987             } else if ($bi = $DB->get_record('block_instance', array('id'=>$instanceid))) {
1988                 if ($bi->pagetype != 'course-view') {
1989                     // ok - not a course block
1990                 } else if ($parent = get_context_instance(CONTEXT_COURSE, $bi->pageid)) {
1991                     $basepath  = $parent->path;
1992                     $basedepth = $parent->depth;
1993                 } else {
1994                     // parent course does not exist - course blocks can not exist without a course
1995                     $result = false;
1996                 }
1997             } else {
1998                 // block does not exist
1999                 $result = false;
2000             }
2001             break;
2002         case CONTEXT_USER:
2003             // default to basepath
2004             break;
2005     }
2007     // if grandparents unknown, maybe rebuild_context_path() will solve it later
2008     if ($basedepth != 0) {
2009         $context->depth = $basedepth+1;
2010     }
2012     if ($result and $id = $DB->insert_record('context', $context)) {
2013         // can't set the full path till we know the id!
2014         if ($basedepth != 0 and !empty($basepath)) {
2015             $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
2016         }
2017         return get_context_instance_by_id($id);
2019     } else {
2020         debugging('Error: could not insert new context level "'.
2021                   s($contextlevel).'", instance "'.
2022                   s($instanceid).'".');
2023         return false;
2024     }
2027 /**
2028  * This hacky function is needed because we can not change system context instanceid using normal upgrade routine.
2029  */
2030 function get_system_context($cache=true) {
2031     global $DB;
2033     static $cached = null;
2034     if ($cache and defined('SYSCONTEXTID')) {
2035         if (is_null($cached)) {
2036             $cached = new object();
2037             $cached->id           = SYSCONTEXTID;
2038             $cached->contextlevel = CONTEXT_SYSTEM;
2039             $cached->instanceid   = 0;
2040             $cached->path         = '/'.SYSCONTEXTID;
2041             $cached->depth        = 1;
2042         }
2043         return $cached;
2044     }
2046     if (!$context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM))) {
2047         $context = new object();
2048         $context->contextlevel = CONTEXT_SYSTEM;
2049         $context->instanceid   = 0;
2050         $context->depth        = 1;
2051         $context->path         = NULL; //not known before insert
2053         if (!$context->id = $DB->insert_record('context', $context)) {
2054             // better something than nothing - let's hope it will work somehow
2055             // DONT do it if we're cli because it's IMMUNTABLE.  Doing it during web installer works because
2056             // each step is a new request
2057             if (!defined('SYSCONTEXTID') && !defined('CLI_UPGRADE')) {
2058                 define('SYSCONTEXTID', 1);
2059                 $context->id   = SYSCONTEXTID;
2060                 $context->path = '/'.SYSCONTEXTID;
2061             } else {
2062                 $context->id   = 0;
2063                 $context->path = '/0';
2064             }
2065             debugging('Can not create system context');
2066             return $context;
2067         }
2068     }
2070     if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
2071         $context->instanceid   = 0;
2072         $context->path         = '/'.$context->id;
2073         $context->depth        = 1;
2074         $DB->update_record('context', $context);
2075     }
2077     if (!defined('SYSCONTEXTID')) {
2078         define('SYSCONTEXTID', $context->id);
2079     }
2081     $cached = $context;
2082     return $cached;
2085 /**
2086  * Remove a context record and any dependent entries,
2087  * removes context from static context cache too
2088  * @param $level
2089  * @param $instanceid
2090  *
2091  * @return bool properly deleted
2092  */
2093 function delete_context($contextlevel, $instanceid) {
2094     global $context_cache, $context_cache_id, $DB;
2096     // do not use get_context_instance(), because the related object might not exist,
2097     // or the context does not exist yet and it would be created now
2098     if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
2099         $result = $DB->delete_records('role_assignments', array('contextid'=>$context->id)) &&
2100                   $DB->delete_records('role_capabilities', array('contextid'=>$context->id)) &&
2101                   $DB->delete_records('context', array('id'=>$context->id));
2103         // do not mark dirty contexts if parents unknown
2104         if (!is_null($context->path) and $context->depth > 0) {
2105             mark_context_dirty($context->path);
2106         }
2108         // purge static context cache if entry present
2109         unset($context_cache[$contextlevel][$instanceid]);
2110         unset($context_cache_id[$context->id]);
2112         return $result;
2113     } else {
2115         return true;
2116     }
2119 /**
2120  * Precreates all contexts including all parents
2121  * @param int $contextlevel, empty means all
2122  * @param bool $buildpaths update paths and depths
2123  * @return void
2124  */
2125 function create_contexts($contextlevel=null, $buildpaths=true) {
2126     global $DB;
2128     //make sure system context exists
2129     $syscontext = get_system_context(false);
2131     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2132                              or $contextlevel == CONTEXT_COURSE
2133                              or $contextlevel == CONTEXT_MODULE
2134                              or $contextlevel == CONTEXT_BLOCK) {
2135         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2136                 SELECT ".CONTEXT_COURSECAT.", cc.id
2137                   FROM {course}_categories cc
2138                  WHERE NOT EXISTS (SELECT 'x'
2139                                      FROM {context} cx
2140                                     WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
2141         $DB->execute($sql);
2143     }
2145     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2146                              or $contextlevel == CONTEXT_MODULE
2147                              or $contextlevel == CONTEXT_BLOCK) {
2148         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2149                 SELECT ".CONTEXT_COURSE.", c.id
2150                   FROM {course} c
2151                  WHERE NOT EXISTS (SELECT 'x'
2152                                      FROM {context} cx
2153                                     WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
2154         $DB->execute($sql);
2156     }
2158     if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE) {
2159         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2160                 SELECT ".CONTEXT_MODULE.", cm.id
2161                   FROM {course}_modules cm
2162                  WHERE NOT EXISTS (SELECT 'x'
2163                                      FROM {context} cx
2164                                     WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
2165         $DB->execute($sql);
2166     }
2168     if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
2169         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2170                 SELECT ".CONTEXT_BLOCK.", bi.id
2171                   FROM {block_instance} bi
2172                  WHERE NOT EXISTS (SELECT 'x'
2173                                      FROM {context} cx
2174                                     WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2175         $DB->execute($sql);
2176     }
2178     if (empty($contextlevel) or $contextlevel == CONTEXT_USER) {
2179         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2180                 SELECT ".CONTEXT_USER.", u.id
2181                   FROM {user} u
2182                  WHERE u.deleted=0
2183                    AND NOT EXISTS (SELECT 'x'
2184                                      FROM {context} cx
2185                                     WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
2186         $DB->execute($sql);
2188     }
2190     if ($buildpaths) {
2191         build_context_path(false);
2192     }
2195 /**
2196  * Remove stale context records
2197  *
2198  * @return bool
2199  */
2200 function cleanup_contexts() {
2201     global $DB;
2203     $sql = "  SELECT c.contextlevel,
2204                      c.instanceid AS instanceid
2205                 FROM {context} c
2206                 LEFT OUTER JOIN {course}_categories t
2207                      ON c.instanceid = t.id
2208                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
2209             UNION
2210               SELECT c.contextlevel,
2211                      c.instanceid
2212                 FROM {context} c
2213                 LEFT OUTER JOIN {course} t
2214                      ON c.instanceid = t.id
2215                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
2216             UNION
2217               SELECT c.contextlevel,
2218                      c.instanceid
2219                 FROM {context} c
2220                 LEFT OUTER JOIN {course}_modules t
2221                      ON c.instanceid = t.id
2222                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
2223             UNION
2224               SELECT c.contextlevel,
2225                      c.instanceid
2226                 FROM {context} c
2227                 LEFT OUTER JOIN {user} t
2228                      ON c.instanceid = t.id
2229                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
2230             UNION
2231               SELECT c.contextlevel,
2232                      c.instanceid
2233                 FROM {context} c
2234                 LEFT OUTER JOIN {block_instance} t
2235                      ON c.instanceid = t.id
2236                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
2237             UNION
2238               SELECT c.contextlevel,
2239                      c.instanceid
2240                 FROM {context} c
2241                 LEFT OUTER JOIN {groups} t
2242                      ON c.instanceid = t.id
2243                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_GROUP."
2244            ";
2245     if ($rs = $DB->get_recordset_sql($sql)) {
2246         $DB->begin_sql();
2247         $ok = true;
2248         foreach ($rs as $ctx) {
2249             if (!delete_context($ctx->contextlevel, $ctx->instanceid)) {
2250                 $ok = false;
2251                 break;
2252             }
2253         }
2254         $rs->close();
2255         if ($ok) {
2256             $DB->commit_sql();
2257             return true;
2258         } else {
2259             $DB->rollback_sql();
2260             return false;
2261         }
2262     }
2263     return true;
2266 /**
2267  * Get the context instance as an object. This function will create the
2268  * context instance if it does not exist yet.
2269  * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2270  * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2271  *      for $level = CONTEXT_MODULE, this would be $cm->id. And so on.
2272  * @return object The context object.
2273  */
2274 function get_context_instance($contextlevel, $instance=0) {
2276     global $context_cache, $context_cache_id, $DB;
2277     static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_GROUP, CONTEXT_MODULE, CONTEXT_BLOCK);
2279     if ($contextlevel === 'clearcache') {
2280         // TODO: Remove for v2.0
2281         // No longer needed, but we'll catch it to avoid erroring out on custom code.
2282         // This used to be a fix for MDL-9016
2283         // "Restoring into existing course, deleting first
2284         //  deletes context and doesn't recreate it"
2285         return false;
2286     }
2288 /// System context has special cache
2289     if ($contextlevel == CONTEXT_SYSTEM) {
2290         return get_system_context();
2291     }
2293 /// check allowed context levels
2294     if (!in_array($contextlevel, $allowed_contexts)) {
2295         // fatal error, code must be fixed - probably typo or switched parameters
2296         print_error('invalidcourselevel');
2297     }
2299     if (!is_array($instance)) {
2300     /// Check the cache
2301         if (isset($context_cache[$contextlevel][$instance])) {  // Already cached
2302             return $context_cache[$contextlevel][$instance];
2303         }
2305     /// Get it from the database, or create it
2306         if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
2307             $context = create_context($contextlevel, $instance);
2308         }
2310     /// Only add to cache if context isn't empty.
2311         if (!empty($context)) {
2312             $context_cache[$contextlevel][$instance] = $context;    // Cache it for later
2313             $context_cache_id[$context->id]          = $context;    // Cache it for later
2314         }
2316         return $context;
2317     }
2320 /// ok, somebody wants to load several contexts to save some db queries ;-)
2321     $instances = $instance;
2322     $result = array();
2324     foreach ($instances as $key=>$instance) {
2325     /// Check the cache first
2326         if (isset($context_cache[$contextlevel][$instance])) {  // Already cached
2327             $result[$instance] = $context_cache[$contextlevel][$instance];
2328             unset($instances[$key]);
2329             continue;
2330         }
2331     }
2333     if ($instances) {
2334         list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2335         array_unshift($params, $contextlevel);
2336         $sql = "SELECT instanceid, id, contextlevel, path, depth
2337                   FROM {context}
2338                  WHERE contextlevel=? AND instanceid $instanceids";
2340         if (!$contexts = $DB->get_records_sql($sql, $params)) {
2341             $contexts = array();
2342         }
2344         foreach ($instances as $instance) {
2345             if (isset($contexts[$instance])) {
2346                 $context = $contexts[$instance];
2347             } else {
2348                 $context = create_context($contextlevel, $instance);
2349             }
2351             if (!empty($context)) {
2352                 $context_cache[$contextlevel][$instance] = $context;    // Cache it for later
2353                 $context_cache_id[$context->id] = $context;             // Cache it for later
2354             }
2356             $result[$instance] = $context;
2357         }
2358     }
2360     return $result;
2364 /**
2365  * Get a context instance as an object, from a given context id.
2366  * @param mixed $id a context id or array of ids.
2367  * @return mixed object or array of the context object.
2368  */
2369 function get_context_instance_by_id($id) {
2370     global $context_cache, $context_cache_id, $DB;
2372     if ($id == SYSCONTEXTID) {
2373         return get_system_context();
2374     }
2376     if (isset($context_cache_id[$id])) {  // Already cached
2377         return $context_cache_id[$id];
2378     }
2380     if ($context = $DB->get_record('context', array('id'=>$id))) {   // Update the cache and return
2381         $context_cache[$context->contextlevel][$context->instanceid] = $context;
2382         $context_cache_id[$context->id] = $context;
2383         return $context;
2384     }
2386     return false;
2390 /**
2391  * Get the local override (if any) for a given capability in a role in a context
2392  * @param $roleid
2393  * @param $contextid
2394  * @param $capability
2395  */
2396 function get_local_override($roleid, $contextid, $capability) {
2397     global $DB;
2398     return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
2403 /************************************
2404  *    DB TABLE RELATED FUNCTIONS    *
2405  ************************************/
2407 /**
2408  * function that creates a role
2409  * @param name - role name
2410  * @param shortname - role short name
2411  * @param description - role description
2412  * @param legacy - optional legacy capability
2413  * @return id or false
2414  */
2415 function create_role($name, $shortname, $description, $legacy='') {
2416     global $DB;
2418     // check for duplicate role name
2420     if ($role = $DB->get_record('role', array('name'=>$name))) {
2421         print_error('duplicaterolename');
2422     }
2424     if ($role = $DB->get_record('role', array('shortname'=>$shortname))) {
2425         print_error('duplicateroleshortname');
2426     }
2428     $role = new object();
2429     $role->name = $name;
2430     $role->shortname = $shortname;
2431     $role->description = $description;
2433     //find free sortorder number
2434     $role->sortorder = $DB->count_records('role');
2435     while ($DB->get_record('role',array('sortorder'=>$role->sortorder))) {
2436         $role->sortorder += 1;
2437     }
2439     if (!$context = get_context_instance(CONTEXT_SYSTEM)) {
2440         return false;
2441     }
2443     if ($id = $DB->insert_record('role', $role)) {
2444         if ($legacy) {
2445             assign_capability($legacy, CAP_ALLOW, $id, $context->id);
2446         }
2448         /// By default, users with role:manage at site level
2449         /// should be able to assign users to this new role, and override this new role's capabilities
2451         // find all admin roles
2452         if ($adminroles = get_roles_with_capability('moodle/role:manage', CAP_ALLOW, $context)) {
2453             // foreach admin role
2454             foreach ($adminroles as $arole) {
2455                 // write allow_assign and allow_overrid
2456                 allow_assign($arole->id, $id);
2457                 allow_override($arole->id, $id);
2458             }
2459         }
2461         return $id;
2462     } else {
2463         return false;
2464     }
2468 /**
2469  * function that deletes a role and cleanups up after it
2470  * @param roleid - id of role to delete
2471  * @return success
2472  */
2473 function delete_role($roleid) {
2474     global $CFG, $DB;
2475     $success = true;
2477 // mdl 10149, check if this is the last active admin role
2478 // if we make the admin role not deletable then this part can go
2480     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
2482     if ($role = $DB->get_record('role', array('id'=>$roleid))) {
2483         if ($DB->record_exists('role_capabilities', array('contextid'=>$systemcontext->id, 'roleid'=>$roleid, 'capability'=>'moodle/site:doanything'))) {
2484             // deleting an admin role
2485             $status = false;
2486             if ($adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW, $systemcontext)) {
2487                 foreach ($adminroles as $adminrole) {
2488                     if ($adminrole->id != $roleid) {
2489                         // some other admin role
2490                         if ($DB->record_exists('role_assignments', array('roleid'=>$adminrole->id, 'contextid'=>$systemcontext->id))) {
2491                             // found another admin role with at least 1 user assigned
2492                             $status = true;
2493                             break;
2494                         }
2495                     }
2496                 }
2497             }
2498             if ($status !== true) {
2499                 error ('You can not delete this role because there is no other admin roles with users assigned');
2500             }
2501         }
2502     }
2504 // first unssign all users
2505     if (!role_unassign($roleid)) {
2506         debugging("Error while unassigning all users from role with ID $roleid!");
2507         $success = false;
2508     }
2510 // cleanup all references to this role, ignore errors
2511     if ($success) {
2512         $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
2513         $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
2514         $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
2515         $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2516         $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2517         $DB->delete_records('role_names',          array('roleid'=>$roleid));
2518     }
2520 // finally delete the role itself
2521     // get this before the name is gone for logging
2522     $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
2524     if ($success and !$DB->delete_records('role', array('id'=>$roleid))) {
2525         debugging("Could not delete role record with ID $roleid!");
2526         $success = false;
2527     }
2529     if ($success) {
2530         add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
2531     }
2533     return $success;
2536 /**
2537  * Function to write context specific overrides, or default capabilities.
2538  * @param module - string name
2539  * @param capability - string name
2540  * @param contextid - context id
2541  * @param roleid - role id
2542  * @param permission - int 1,-1 or -1000
2543  * should not be writing if permission is 0
2544  */
2545 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
2547     global $USER, $DB;
2549     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
2550         unassign_capability($capability, $roleid, $contextid);
2551         return true;
2552     }
2554     $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
2556     if ($existing and !$overwrite) {   // We want to keep whatever is there already
2557         return true;
2558     }
2560     $cap = new object;
2561     $cap->contextid = $contextid;
2562     $cap->roleid = $roleid;
2563     $cap->capability = $capability;
2564     $cap->permission = $permission;
2565     $cap->timemodified = time();
2566     $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
2568     if ($existing) {
2569         $cap->id = $existing->id;
2570         return $DB->update_record('role_capabilities', $cap);
2571     } else {
2572         $c = $DB->get_record('context', array('id'=>$contextid));
2573         return $DB->insert_record('role_capabilities', $cap);
2574     }
2577 /**
2578  * Unassign a capability from a role.
2579  * @param $roleid - the role id
2580  * @param $capability - the name of the capability
2581  * @return boolean - success or failure
2582  */
2583 function unassign_capability($capability, $roleid, $contextid=NULL) {
2584     global $DB;
2586     if (isset($contextid)) {
2587         // delete from context rel, if this is the last override in this context
2588         $status = $DB->delete_records('role_capabilities', array('capability'=>$capability,
2589                 'roleid'=>$roleid, 'contextid'=>$contextid));
2590     } else {
2591         $status = $DB->delete_records('role_capabilities', array('capability'=>$capability,
2592                 'roleid'=>$roleid));
2593     }
2594     return $status;
2598 /**
2599  * Get the roles that have a given capability assigned to it. This function
2600  * does not resolve the actual permission of the capability. It just checks
2601  * for assignment only.
2602  * @param $capability - capability name (string)
2603  * @param $permission - optional, the permission defined for this capability
2604  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT
2605  * @return array or role objects
2606  */
2607 function get_roles_with_capability($capability, $permission=NULL, $context='') {
2609     global $CFG, $DB;
2611     $params = array();
2613     if ($context) {
2614         if ($contexts = get_parent_contexts($context)) {
2615             $listofcontexts = '('.implode(',', $contexts).')';
2616         } else {
2617             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2618             $listofcontexts = '('.$sitecontext->id.')'; // must be site
2619         }
2620         $contextstr = "AND (rc.contextid = ? OR  rc.contextid IN $listofcontexts)";
2621         $params[] = $context->id;
2622     } else {
2623         $contextstr = '';
2624     }
2626     $selectroles = "SELECT r.*
2627                       FROM {role} r,
2628                            {role_capabilities} rc
2629                      WHERE rc.capability = ?
2630                            AND rc.roleid = r.id $contextstr";
2632     array_unshift($params, $capability);
2634     if (isset($permission)) {
2635         $selectroles .= " AND rc.permission = ?";
2636         $params[] = $permission;
2637     }
2638     return $DB->get_records_sql($selectroles, $params);
2642 /**
2643  * This function makes a role-assignment (a role for a user or group in a particular context)
2644  * @param $roleid - the role of the id
2645  * @param $userid - userid
2646  * @param $groupid - group id
2647  * @param $contextid - id of the context
2648  * @param $timestart - time this assignment becomes effective
2649  * @param $timeend - time this assignemnt ceases to be effective
2650  * @uses $USER
2651  * @return id - new id of the assigment
2652  */
2653 function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden=0, $enrol='manual',$timemodified='') {
2654     global $USER, $CFG, $DB;
2655     require_once($CFG->dirroot.'/group/lib.php');
2657 /// Do some data validation
2659     if (empty($roleid)) {
2660         debugging('Role ID not provided');
2661         return false;
2662     }
2664     if (empty($userid) && empty($groupid)) {
2665         debugging('Either userid or groupid must be provided');
2666         return false;
2667     }
2669     if ($userid && !$DB->record_exists('user', array('id'=>$userid))) {
2670         debugging('User ID '.intval($userid).' does not exist!');
2671         return false;
2672     }
2674     if ($groupid && !groups_group_exists($groupid)) {
2675         debugging('Group ID '.intval($groupid).' does not exist!');
2676         return false;
2677     }
2679     if (!$context = get_context_instance_by_id($contextid)) {
2680         debugging('Context ID '.intval($contextid).' does not exist!');
2681         return false;
2682     }
2684     if (($timestart and $timeend) and ($timestart > $timeend)) {
2685         debugging('The end time can not be earlier than the start time');
2686         return false;
2687     }
2689     if (!$timemodified) {
2690         $timemodified = time();
2691     }
2693 /// Check for existing entry
2694     if ($userid) {
2695         $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid));
2696     } else {
2697         $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'groupid'=>$groupid));
2698     }
2700     if (empty($ra)) {             // Create a new entry
2701         $ra = new object();
2702         $ra->roleid = $roleid;
2703         $ra->contextid = $context->id;
2704         $ra->userid = $userid;
2705         $ra->hidden = $hidden;
2706         $ra->enrol = $enrol;
2707     /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2708     /// by repeating queries with the same exact parameters in a 100 secs time window
2709         $ra->timestart = round($timestart, -2);
2710         $ra->timeend = $timeend;
2711         $ra->timemodified = $timemodified;
2712         $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
2714         if (!$ra->id = $DB->insert_record('role_assignments', $ra)) {
2715             return false;
2716         }
2718     } else {                      // We already have one, just update it
2719         $ra->id = $ra->id;
2720         $ra->hidden = $hidden;
2721         $ra->enrol = $enrol;
2722     /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2723     /// by repeating queries with the same exact parameters in a 100 secs time window
2724         $ra->timestart = round($timestart, -2);
2725         $ra->timeend = $timeend;
2726         $ra->timemodified = $timemodified;
2727         $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
2729         if (!$DB->update_record('role_assignments', $ra)) {
2730             return false;
2731         }
2732     }
2734 /// mark context as dirty - modules might use has_capability() in xxx_role_assing()
2735 /// again expensive, but needed
2736     mark_context_dirty($context->path);
2738     if (!empty($USER->id) && $USER->id == $userid) {
2739 /// If the user is the current user, then do full reload of capabilities too.
2740         load_all_capabilities();
2741     }
2743 /// Ask all the modules if anything needs to be done for this user
2744     if ($mods = get_list_of_plugins('mod')) {
2745         foreach ($mods as $mod) {
2746             include_once($CFG->dirroot.'/mod/'.$mod.'/lib.php');
2747             $functionname = $mod.'_role_assign';
2748             if (function_exists($functionname)) {
2749                 $functionname($userid, $context, $roleid);
2750             }
2751         }
2752     }
2754     /// now handle metacourse role assignments if in course context
2755     if ($context->contextlevel == CONTEXT_COURSE) {
2756         if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
2757             foreach ($parents as $parent) {
2758                 sync_metacourse($parent->parent_course);
2759             }
2760         }
2761     }
2763     events_trigger('role_assigned', $ra);
2765     return true;
2769 /**
2770  * Deletes one or more role assignments.   You must specify at least one parameter.
2771  * @param $roleid
2772  * @param $userid
2773  * @param $groupid
2774  * @param $contextid
2775  * @param $enrol unassign only if enrolment type matches, NULL means anything
2776  * @return boolean - success or failure
2777  */
2778 function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NULL) {
2780     global $USER, $CFG, $DB;
2782     $success = true;
2784     $args = array('roleid', 'userid', 'groupid', 'contextid');
2785     $select = array();
2786     $params = array();
2788     foreach ($args as $arg) {
2789         if ($$arg) {
2790             $select[] = "$arg = ?";
2791             $params[] = $$arg;
2792         }
2793     }
2794     if (!empty($enrol)) {
2795         $select[] = "enrol=?";
2796         $params[] = $enrol;
2797     }
2799     if ($select) {
2800         if ($ras = $DB->get_records_select('role_assignments', implode(' AND ', $select), $params)) {
2801             $mods = get_list_of_plugins('mod');
2802             foreach($ras as $ra) {
2803                 $fireevent = false;
2804                 /// infinite loop protection when deleting recursively
2805                 if (!$ra = $DB->get_record('role_assignments', array('id'=>$ra->id))) {
2806                     continue;
2807                 }
2808                 if ($DB->delete_records('role_assignments', array('id'=>$ra->id))) {
2809                     $fireevent = true;
2810                 } else {
2811                     $success = false;
2812                 }
2814                 if (!$context = get_context_instance_by_id($ra->contextid)) {
2815                     // strange error, not much to do
2816                     continue;
2817                 }
2819                 /* mark contexts as dirty here, because we need the refreshed
2820                  * caps bellow to delete group membership and user_lastaccess!
2821                  * and yes, this is very expensive for bulk operations :-(
2822                  */
2823                 mark_context_dirty($context->path);
2825                 /// If the user is the current user, then do full reload of capabilities too.
2826                 if (!empty($USER->id) && $USER->id == $ra->userid) {
2827                     load_all_capabilities();
2828                 }
2830                 /// Ask all the modules if anything needs to be done for this user
2831                 foreach ($mods as $mod) {
2832                     include_once($CFG->dirroot.'/mod/'.$mod.'/lib.php');
2833                     $functionname = $mod.'_role_unassign';
2834                     if (function_exists($functionname)) {
2835                         $functionname($ra->userid, $context); // watch out, $context might be NULL if something goes wrong
2836                     }
2837                 }
2839                 /// now handle metacourse role unassigment and removing from goups if in course context
2840                 if ($context->contextlevel == CONTEXT_COURSE) {
2842                     // cleanup leftover course groups/subscriptions etc when user has
2843                     // no capability to view course
2844                     // this may be slow, but this is the proper way of doing it
2845                     if (!has_capability('moodle/course:view', $context, $ra->userid)) {
2846                         // remove from groups
2847                         groups_delete_group_members($context->instanceid, $ra->userid);
2849                         // delete lastaccess records
2850                         $DB->delete_records('user_lastaccess', array('userid'=>$ra->userid, 'courseid'=>$context->instanceid));
2851                     }
2853                     //unassign roles in metacourses if needed
2854                     if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
2855                         foreach ($parents as $parent) {
2856                             sync_metacourse($parent->parent_course);
2857                         }
2858                     }
2859                 }
2861                 if ($fireevent) {
2862                     events_trigger('role_unassigned', $ra);
2863                 }
2864             }
2865         }
2866     }
2868     return $success;
2871 /**
2872  * A convenience function to take care of the common case where you
2873  * just want to enrol someone using the default role into a course
2874  *
2875  * @param object $course
2876  * @param object $user
2877  * @param string $enrol - the plugin used to do this enrolment
2878  */
2879 function enrol_into_course($course, $user, $enrol) {
2881     $timestart = time();
2882     // remove time part from the timestamp and keep only the date part
2883     $timestart = make_timestamp(date('Y', $timestart), date('m', $timestart), date('d', $timestart), 0, 0, 0);
2884     if ($course->enrolperiod) {
2885         $timeend = $timestart + $course->enrolperiod;
2886     } else {
2887         $timeend = 0;
2888     }
2890     if ($role = get_default_course_role($course)) {
2892         $context = get_context_instance(CONTEXT_COURSE, $course->id);
2894         if (!role_assign($role->id, $user->id, 0, $context->id, $timestart, $timeend, 0, $enrol)) {
2895             return false;
2896         }
2898         // force accessdata refresh for users visiting this context...
2899         mark_context_dirty($context->path);
2901         email_welcome_message_to_user($course, $user);
2903         add_to_log($course->id, 'course', 'enrol',
2904                 'view.php?id='.$course->id, $course->id);
2906         return true;
2907     }
2909     return false;
2912 /**
2913  * Loads the capability definitions for the component (from file). If no
2914  * capabilities are defined for the component, we simply return an empty array.
2915  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
2916  * @return array of capabilities
2917  */
2918 function load_capability_def($component) {
2919     global $CFG;
2921     if ($component == 'moodle') {
2922         $defpath = $CFG->libdir.'/db/access.php';
2923         $varprefix = 'moodle';
2924     } else {
2925         $compparts = explode('/', $component);
2927         if ($compparts[0] == 'block') {
2928             // Blocks are an exception. Blocks directory is 'blocks', and not
2929             // 'block'. So we need to jump through hoops.
2930             $defpath = $CFG->dirroot.'/'.$compparts[0].
2931                                 's/'.$compparts[1].'/db/access.php';
2932             $varprefix = $compparts[0].'_'.$compparts[1];
2934         } else if ($compparts[0] == 'format') {
2935             // Similar to the above, course formats are 'format' while they
2936             // are stored in 'course/format'.
2937             $defpath = $CFG->dirroot.'/course/'.$component.'/db/access.php';
2938             $varprefix = $compparts[0].'_'.$compparts[1];
2940         } else if ($compparts[0] == 'gradeimport') {
2941             $defpath = $CFG->dirroot.'/grade/import/'.$compparts[1].'/db/access.php';
2942             $varprefix = $compparts[0].'_'.$compparts[1];
2944         } else if ($compparts[0] == 'gradeexport') {
2945             $defpath = $CFG->dirroot.'/grade/export/'.$compparts[1].'/db/access.php';
2946             $varprefix = $compparts[0].'_'.$compparts[1];
2948         } else if ($compparts[0] == 'gradereport') {
2949             $defpath = $CFG->dirroot.'/grade/report/'.$compparts[1].'/db/access.php';
2950             $varprefix = $compparts[0].'_'.$compparts[1];
2952         } else {
2953             $defpath = $CFG->dirroot.'/'.$component.'/db/access.php';
2954             $varprefix = str_replace('/', '_', $component);
2955         }
2956     }
2957     $capabilities = array();
2959     if (file_exists($defpath)) {
2960         require($defpath);
2961         $capabilities = ${$varprefix.'_capabilities'};
2962     }
2963     return $capabilities;
2967 /**
2968  * Gets the capabilities that have been cached in the database for this
2969  * component.
2970  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
2971  * @return array of capabilities
2972  */
2973 function get_cached_capabilities($component='moodle') {
2974     global $DB;
2976     if ($component == 'moodle') {
2977         $storedcaps = $DB->get_records_select('capabilities', "name LIKE ?", array('moodle/%:%'));
2979     } else if ($component == 'local') {
2980         $storedcaps = $DB->get_records_select('capabilities', "name LIKE ?", array('moodle/local:%'));
2982     } else {
2983         $storedcaps = $DB->get_records_select('capabilities', "name LIKE ?", array("$component:%"));
2984     }
2986     return $storedcaps;
2989 /**
2990  * Returns default capabilities for given legacy role type.
2991  *
2992  * @param string legacy role name
2993  * @return array
2994  */
2995 function get_default_capabilities($legacyrole) {
2996     global $DB;
2997     if (!$allcaps = $DB->get_records('capabilities')) {
2998         print_error('nocaps', 'debug');
2999     }
3000     $alldefs = array();
3001     $defaults = array();
3002     $components = array();
3003     foreach ($allcaps as $cap) {
3004         if (!in_array($cap->component, $components)) {
3005             $components[] = $cap->component;
3006             $alldefs = array_merge($alldefs, load_capability_def($cap->component));
3007         }
3008     }
3009     foreach($alldefs as $name=>$def) {
3010         if (isset($def['legacy'][$legacyrole])) {
3011             $defaults[$name] = $def['legacy'][$legacyrole];
3012         }
3013     }
3015     //some exceptions
3016     $defaults['moodle/legacy:'.$legacyrole] = CAP_ALLOW;
3017     if ($legacyrole == 'admin') {
3018         $defaults['moodle/site:doanything'] = CAP_ALLOW;
3019     }
3020     return $defaults;
3023 /**
3024  * Reset role capabilitites to default according to selected legacy capability.
3025  * If several legacy caps selected, use the first from get_default_capabilities.
3026  * If no legacy selected, removes all capabilities.
3027  *
3028  * @param int @roleid
3029  */
3030 function reset_role_capabilities($roleid) {
3031     global $DB;
3033     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
3034     $legacyroles = get_legacy_roles();
3036     $defaultcaps = array();
3037     foreach($legacyroles as $ltype=>$lcap) {
3038         $localoverride = get_local_override($roleid, $sitecontext->id, $lcap);
3039         if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) {
3040             //choose first selected legacy capability
3041             $defaultcaps = get_default_capabilities($ltype);
3042             break;
3043         }
3044     }
3046     $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
3047     if (!empty($defaultcaps)) {
3048         foreach($defaultcaps as $cap=>$permission) {
3049             assign_capability($cap, $permission, $roleid, $sitecontext->id);
3050         }
3051     }
3054 /**
3055  * Updates the capabilities table with the component capability definitions.
3056  * If no parameters are given, the function updates the core moodle
3057  * capabilities.
3058  *
3059  * Note that the absence of the db/access.php capabilities definition file
3060  * will cause any stored capabilities for the component to be removed from
3061  * the database.
3062  *
3063  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3064  * @return boolean
3065  */
3066 function update_capabilities($component='moodle') {
3067     global $DB;
3069     $storedcaps = array();
3071     $filecaps = load_capability_def($component);
3072     $cachedcaps = get_cached_capabilities($component);
3073     if ($cachedcaps) {
3074         foreach ($cachedcaps as $cachedcap) {
3075             array_push($storedcaps, $cachedcap->name);
3076             // update risk bitmasks and context levels in existing capabilities if needed
3077             if (array_key_exists($cachedcap->name, $filecaps)) {
3078                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
3079                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
3080                 }
3081                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
3082                     $updatecap = new object();
3083                     $updatecap->id = $cachedcap->id;
3084                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
3085                     if (!$DB->update_record('capabilities', $updatecap)) {
3086                         return false;
3087                     }
3088                 }
3090                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
3091                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
3092                 }
3093                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
3094                     $updatecap = new object();
3095                     $updatecap->id = $cachedcap->id;
3096                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
3097                     if (!$DB->update_record('capabilities', $updatecap)) {
3098                         return false;
3099                     }
3100                 }
3101             }
3102         }
3103     }
3105     // Are there new capabilities in the file definition?
3106     $newcaps = array();
3108     foreach ($filecaps as $filecap => $def) {
3109         if (!$storedcaps ||
3110                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3111             if (!array_key_exists('riskbitmask', $def)) {
3112                 $def['riskbitmask'] = 0; // no risk if not specified
3113             }
3114             $newcaps[$filecap] = $def;
3115         }
3116     }
3117     // Add new capabilities to the stored definition.
3118     foreach ($newcaps as $capname => $capdef) {
3119         $capability = new object;
3120         $capability->name = $capname;
3121         $capability->captype = $capdef['captype'];
3122         $capability->contextlevel = $capdef['contextlevel'];
3123         $capability->component = $component;
3124         $capability->riskbitmask = $capdef['riskbitmask'];
3126         if (!$DB->insert_record('capabilities', $capability, false)) {
3127             return false;
3128         }
3131         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3132             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
3133                 foreach ($rolecapabilities as $rolecapability){
3134                     //assign_capability will update rather than insert if capability exists
3135                     if (!assign_capability($capname, $rolecapability->permission,
3136                                             $rolecapability->roleid, $rolecapability->contextid, true)){
3137                          notify('Could not clone capabilities for '.$capname);
3138                     }
3139                 }
3140             }
3141         // Do we need to assign the new capabilities to roles that have the
3142         // legacy capabilities moodle/legacy:* as well?
3143         // we ignore legacy key if we have cloned permissions
3144         } else if (isset($capdef['legacy']) && is_array($capdef['legacy']) &&
3145                     !assign_legacy_capabilities($capname, $capdef['legacy'])) {
3146             notify('Could not assign legacy capabilities for '.$capname);
3147         }
3148     }
3149     // Are there any capabilities that have been removed from the file
3150     // definition that we need to delete from the stored capabilities and
3151     // role assignments?
3152     capabilities_cleanup($component, $filecaps);
3154     return true;
3158 /**
3159  * Deletes cached capabilities that are no longer needed by the component.
3160  * Also unassigns these capabilities from any roles that have them.
3161  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
3162  * @param $newcapdef - array of the new capability definitions that will be
3163  *                     compared with the cached capabilities
3164  * @return int - number of deprecated capabilities that have been removed
3165  */
3166 function capabilities_cleanup($component, $newcapdef=NULL) {
3167     global $DB;
3169     $removedcount = 0;
3171     if ($cachedcaps = get_cached_capabilities($component)) {
3172         foreach ($cachedcaps as $cachedcap) {
3173             if (empty($newcapdef) ||
3174                         array_key_exists($cachedcap->name, $newcapdef) === false) {
3176                 // Remove from capabilities cache.
3177                 if (!$DB->delete_records('capabilities', array('name'=>$cachedcap->name))) {
3178                     print_error('cannotdeletecap', '', '', $cachedcap->name);
3179                 } else {
3180                     $removedcount++;
3181                 }
3182                 // Delete from roles.
3183                 if ($roles = get_roles_with_capability($cachedcap->name)) {
3184                     foreach($roles as $role) {
3185                         if (!unassign_capability($cachedcap->name, $role->id)) {
3186                             print_error('cannotunassigncap', '', '', array($cachedcap->name, $role->name));
3187                         }
3188                     }
3189                 }
3190             } // End if.
3191         }
3192     }
3193     return $removedcount;
3198 /****************
3199  * UI FUNCTIONS *
3200  ****************/
3203 /**
3204  * prints human readable context identifier.
3205  */
3206 function print_context_name($context, $withprefix = true, $short = false) {
3207     global $DB;
3209     $name = '';
3210     switch ($context->contextlevel) {
3212         case CONTEXT_SYSTEM: // by now it's a definite an inherit
3213             $name = get_string('coresystem');
3214             break;
3216         case CONTEXT_USER:
3217             if ($user = $DB->get_record('user', array('id'=>$context->instanceid))) {
3218                 if ($withprefix){
3219                     $name = get_string('user').': ';
3220                 }
3221                 $name .= fullname($user);
3222             }
3223             break;
3225         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
3226             if ($category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) {
3227                 if ($withprefix){
3228                     $name = get_string('category').': ';
3229                 }
3230                 $name .=format_string($category->name);
3231             }
3232             break;
3234         case CONTEXT_COURSE: // 1 to 1 to course cat
3235             if ($course = $DB->get_record('course', array('id'=>$context->instanceid))) {
3236                 if ($withprefix){
3237                     if ($context->instanceid == SITEID) {
3238                         $name = get_string('site').': ';
3239                     } else {
3240                         $name = get_string('course').': ';
3241                     }
3242                 }
3243                 if ($short){
3244                     $name .=format_string($course->shortname);
3245                 } else {
3246                     $name .=format_string($course->fullname);
3247                }
3249             }
3250             break;
3252         case CONTEXT_GROUP: // 1 to 1 to course
3253             if ($name = groups_get_group_name($context->instanceid)) {
3254                 if ($withprefix){
3255                     $name = get_string('group').': '. $name;
3256                 }
3257             }
3258             break;
3260         case CONTEXT_MODULE: // 1 to 1 to course
3261             if ($cm = $DB->get_record('course_modules', array('id'=>$context->instanceid))) {
3262                 if ($module = $DB->get_record('modules', array('id'=>$cm->module))) {
3263                     if ($mod = $DB->get_record($module->name, array('id'=>$cm->instance))) {
3264                         if ($withprefix){
3265                             $name = get_string('activitymodule').': ';
3266                         }
3267                         $name .= $mod->name;
3268                     }
3269                 }
3270             }
3271             break;
3273         case CONTEXT_BLOCK: // not necessarily 1 to 1 to course
3274             if ($blockinstance = $DB->get_record('block_instance', array('id'=>$context->instanceid))) {
3275                 if ($block = $DB->get_record('block', array('id'=>$blockinstance->blockid))) {
3276                     global $CFG;
3277                     require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3278                     require_once("$CFG->dirroot/blocks/$block->name/block_$block->name.php");
3279                     $blockname = "block_$block->name";
3280                     if ($blockobject = new $blockname()) {
3281                         if ($withprefix){
3282                             $name = get_string('block').': ';
3283                         }
3284                         $name .= $blockobject->title;
3285                     }
3286                 }
3287             }
3288             break;
3290         default:
3291             error ('This is an unknown context (' . $context->contextlevel . ') in print_context_name!');
3292             return false;
3294     }
3295     return $name;
3299 /**
3300  * Extracts the relevant capabilities given a contextid.
3301  * All case based, example an instance of forum context.
3302  * Will fetch all forum related capabilities, while course contexts
3303  * Will fetch all capabilities
3304  * @param object context
3305  * @return array();
3306  *
3307  *  capabilities
3308  * `name` varchar(150) NOT NULL,
3309  * `captype` varchar(50) NOT NULL,
3310  * `contextlevel` int(10) NOT NULL,
3311  * `component` varchar(100) NOT NULL,
3312  */
3313 function fetch_context_capabilities($context) {
3314     global $DB, $CFG;
3316     $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
3318     $params = array();
3320     switch ($context->contextlevel) {
3322         case CONTEXT_SYSTEM: // all
3323             $SQL = "SELECT *
3324                       FROM {capabilities}";
3325         break;
3327         case CONTEXT_USER:
3328             $extracaps = array('moodle/grade:viewall');
3329             list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3330             $SQL = "SELECT *
3331                       FROM {capabilities}
3332                      WHERE contextlevel = ".CONTEXT_USER."
3333                            OR name $extra";
3334         break;
3336         case CONTEXT_COURSECAT: // course category context and bellow
3337             $SQL = "SELECT *
3338                       FROM {capabilities}
3339                      WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3340         break;
3342         case CONTEXT_COURSE: // course context and bellow
3343             $SQL = "SELECT *
3344                       FROM {capabilities}
3345                      WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3346         break;
3348         case CONTEXT_MODULE: // mod caps
3349             $cm = $DB->get_record('course_modules', array('id'=>$context->instanceid));
3350             $module = $DB->get_record('modules', array('id'=>$cm->module));
3352             $extra = "";
3353             $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
3354             if (file_exists($modfile)) {
3355                 include_once($modfile);
3356                 $modfunction = $module->name.'_get_extra_capabilities';
3357                 if (function_exists($modfunction)) {
3358                     if ($extracaps = $modfunction()) {
3359                         list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3360                         $extra = "OR name $extra";
3361                     }
3362                 }
3363             }
3365             $SQL = "SELECT *
3366                       FROM {capabilities}
3367                      WHERE contextlevel = ".CONTEXT_MODULE."
3368                            AND component = :component
3369                            $extra";
3370             $params['component'] = "mod/$module->name";
3371         break;
3373         case CONTEXT_BLOCK: // block caps
3374             $cb = $DB->get_record('block_instance', array('id'=>$context->instanceid));
3375             $block = $DB->get_record('block', array('id'=>$cb->blockid));
3377             $extra = "";
3378             if ($blockinstance = block_instance($block->name)) {
3379                 if ($extracaps = $blockinstance->get_extra_capabilities()) {
3380                     list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3381                     $extra = "OR name $extra";
3382                 }
3383             }
3385             $SQL = "SELECT *
3386                       FROM {capabilities}
3387                      WHERE (contextlevel = ".CONTEXT_BLOCK."
3388                            AND component = :component)
3389                            $extra";
3390             $params['component'] = "block/$block->name";
3391         break;
3393         default:
3394         return false;
3395     }
3397     if (!$records = $DB->get_records_sql($SQL.' '.$sort, $params)) {
3398         $records = array();
3399     }
3401     return $records;
3405 /**
3406  * This function pulls out all the resolved capabilities (overrides and
3407  * defaults) of a role used in capability overrides in contexts at a given
3408  * context.
3409  * @param obj $context
3410  * @param int $roleid
3411  * @param bool self - if set to true, resolve till this level, else stop at immediate parent level
3412  * @return array
3413  */
3414 function role_context_capabilities($roleid, $context, $cap='') {
3415     global $DB;
3417     $contexts = get_parent_contexts($context);
3418     $contexts[] = $context->id;
3419     $contexts = '('.implode(',', $contexts).')';
3421     $params = array($roleid);
3423     if ($cap) {
3424         $search = " AND rc.capability = ? ";
3425         $params[] = $cap;
3426     } else {
3427         $search = '';
3428     }
3430     $sql = "SELECT rc.*
3431               FROM {role_capabilities} rc, {context} c
3432              WHERE rc.contextid in $contexts
3433                    AND rc.roleid = ?
3434                    AND rc.contextid = c.id $search
3435           ORDER BY c.contextlevel DESC, rc.capability DESC";
3437     $capabilities = array();
3439     if ($records = $DB->get_records_sql($sql, $params)) {
3440         // We are traversing via reverse order.
3441         foreach ($records as $record) {
3442             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
3443             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
3444                 $capabilities[$record->capability] = $record->permission;
3445             }
3446         }
3447     }
3448     return $capabilities;
3451 /**
3452  * Recursive function which, given a context, find all parent context ids,
3453  * and return the array in reverse order, i.e. parent first, then grand
3454  * parent, etc.
3455  *
3456  * @param object $context
3457  * @return array()
3458  */
3459 function get_parent_contexts($context) {
3461     if ($context->path == '') {
3462         return array();
3463     }
3465     $parentcontexts = substr($context->path, 1); // kill leading slash
3466     $parentcontexts = explode('/', $parentcontexts);
3467     array_pop($parentcontexts); // and remove its own id
3469     return array_reverse($parentcontexts);
3472 /**
3473  * Return the id of the parent of this context, or false if there is no parent (only happens if this
3474  * is the site context.)
3475  *
3476  * @param object $context
3477  * @return integer the id of the parent context.
3478  */
3479 function get_parent_contextid($context) {
3480     $parentcontexts = get_parent_contexts($context);
3481     if (count($parentcontexts) == 0) {
3482         return false;
3483     }
3484     return array_shift($parentcontexts);
3487 /**
3488  * Recursive function which, given a context, find all its children context ids.
3489  *
3490  * When called for a course context, it will return the modules and blocks
3491  * displayed in the course page.
3492  *
3493  * For course category contexts it will return categories and courses. It will
3494  * NOT recurse into courses - if you want to do that, call it on the returned
3495  * courses.
3496  *
3497  * If called on a course context it _will_ populate the cache with the appropriate
3498  * contexts ;-)
3499  *
3500  * @param object $context.
3501  * @return array of child records
3502  */
3503 function get_child_contexts($context) {
3505     global $CFG, $context_cache, $DB;
3507     // We *MUST* populate the context_cache as the callers
3508     // will probably ask for the full record anyway soon after
3509     // soon after calling us ;-)
3511     switch ($context->contextlevel) {
3513         case CONTEXT_BLOCK:
3514             // No children.
3515             return array();
3516         break;
3518         case CONTEXT_MODULE:
3519             // No children.
3520             return array();
3521         break;
3523         case CONTEXT_GROUP:
3524             // No children.
3525             return array();
3526         break;
3528         case CONTEXT_COURSE:
3529             // Find
3530             // - module instances - easy
3531             // - groups
3532             // - blocks assigned to the course-view page explicitly - easy
3533             // - blocks pinned (note! we get all of them here, regardless of vis)
3534             $sql = " SELECT ctx.*
3535                        FROM {context} ctx
3536                       WHERE ctx.path LIKE ?
3537                             AND ctx.contextlevel IN (".CONTEXT_MODULE.",".CONTEXT_BLOCK.")
3538                     UNION
3539                      SELECT ctx.*
3540                        FROM {context} ctx
3541                        JOIN {groups}  g ON (ctx.instanceid=g.id AND ctx.contextlevel=".CONTEXT_GROUP.")
3542                       WHERE g.courseid=?
3543                     UNION
3544                      SELECT ctx.*
3545                        FROM {context}      ctx
3546                        JOIN {block_pinned} b ON (ctx.instanceid=b.blockid AND ctx.contextlevel=".CONTEXT_BLOCK.")
3547                       WHERE b.pagetype='course-view'";
3548             $params = array("{$context->path}/%", $context->instanceid);
3549             $records = array();
3550             if ($rs = $DB->get_recordset_sql($sql, $params)) {
3551                 foreach ($rs as $rec) {
3552                     $records[$rec->id] = $rec;
3553                     $context_cache[$rec->contextlevel][$rec->instanceid] = $rec;
3554                 }
3555                 $rs->close();
3556             }
3557             return $records;
3558         break;
3560         case CONTEXT_COURSECAT:
3561             // Find
3562             // - categories
3563             // - courses
3564             $sql = " SELECT ctx.*
3565                        FROM {context} ctx
3566                       WHERE ctx.path LIKE ?
3567                             AND ctx.contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.")";
3568             $params = array("{$context->path}/%");
3569             $records = array();
3570             if ($rs = $DB->get_recordset_sql($sql, $params)) {
3571                 foreach ($rs as $rec) {
3572                     $records[$rec->id] = $rec;
3573                     $context_cache[$rec->contextlevel][$rec->instanceid] = $rec;
3574                 }
3575                 $rs->close();
3576             }
3577             return $records;
3578         break;
3580         case CONTEXT_USER:
3581             // No children.
3582             return array();
3583         break;
3585         case CONTEXT_SYSTEM:
3586             // Just get all the contexts except for CONTEXT_SYSTEM level
3587             // and hope we don't OOM in the process - don't cache
3588             $sql = "SELECT c.*
3589                       FROM {context} c
3590                      WHERE contextlevel != ".CONTEXT_SYSTEM;
3592             return $DB->get_records_sql($sql);
3593         break;
3595         default:
3596             print_error('unknowcontext', '', '', $context->contextlevel);
3597         return false;
3598     }
3602 /**
3603  * Gets a string for sql calls, searching for stuff in this context or above
3604  * @param object $context
3605  * @return string
3606  */
3607 function get_related_contexts_string($context) {
3608     if ($parents = get_parent_contexts($context)) {
3609         return (' IN ('.$context->id.','.implode(',', $parents).')');
3610     } else {
3611         return (' ='.$context->id);
3612     }
3615 /**
3616  * Returns the human-readable, translated version of the capability.
3617  * Basically a big switch statement.
3618  * @param $capabilityname - e.g. mod/choice:readresponses
3619  */
3620 function get_capability_string($capabilityname) {
3622     // Typical capabilityname is mod/choice:readresponses
3624     $names = split('/', $capabilityname);
3625     $stringname = $names[1];                 // choice:readresponses
3626     $components = split(':', $stringname);
3627     $componentname = $components[0];               // choice
3629     switch ($names[0]) {
3630         case 'mod':
3631             $string = get_string($stringname, $componentname);
3632         break;
3634         case 'block':
3635             $string = get_string($stringname, 'block_'.$componentname);
3636         break;
3638         case 'moodle':
3639             if ($componentname == 'local') {
3640                 $string = get_string($stringname, 'local');
3641             } else {
3642                 $string = get_string($stringname, 'role');
3643             }
3644         break;
3646         case 'enrol':
3647             $string = get_string($stringname, 'enrol_'.$componentname);
3648         break;
3650         case 'format':
3651             $string = get_string($stringname, 'format_'.$componentname);
3652         break;
3654         case 'gradeexport':
3655             $string = get_string($stringname, 'gradeexport_'.$componentname);
3656         break;
3658         case 'gradeimport':
3659             $string = get_string($stringname, 'gradeimport_'.$componentname);
3660         break;
3662         case 'gradereport':
3663             $string = get_string($stringname, 'gradereport_'.$componentname);
3664         break;
3666         default:
3667             $string = get_string($stringname);
3668         break;
3670     }
3671     return $string;
3675 /**
3676  * This gets the mod/block/course/core etc strings.
3677  * @param $component
3678  * @param $contextlevel
3679  */
3680 function get_component_string($component, $contextlevel) {
3682     switch ($contextlevel) {
3684         case CONTEXT_SYSTEM:
3685             if (preg_match('|^enrol/|', $component)) {
3686                 $langname = str_replace('/', '_', $component);
3687                 $string = get_string('enrolname', $langname);
3688             } else if (preg_match('|^block/|', $component)) {
3689                 $langname = str_replace('/', '_', $component);
3690                 $string = get_string('blockname', $langname);
3691             } else if (preg_match('|^local|', $component)) {
3692                 $langname = str_replace('/', '_', $component);
3693                 $string = get_string('local');
3694             } else {
3695                 $string = get_string('coresystem');
3696             }
3697         break;
3699         case CONTEXT_USER:
3700             $string = get_string('users');
3701         break;
3703         case CONTEXT_COURSECAT:
3704             $string = get_string('categories');
3705         break;
3707         case CONTEXT_COURSE:
3708             if (preg_match('|^gradeimport/|', $component)
3709                 || preg_match('|^gradeexport/|', $component)
3710                 || preg_match('|^gradereport/|', $component)) {
3711                 $string = get_string('gradebook', 'admin');
3712             } else {
3713                 $string = get_string('course');
3714             }
3715         break;
3717         case CONTEXT_GROUP:
3718             $string = get_string('group');
3719         break;
3721         case CONTEXT_MODULE:
3722             $string = get_string('modulename', basename($component));
3723         break;
3725         case CONTEXT_BLOCK:
3726             if( $component == 'moodle' ){
3727                 $string = get_string('block');
3728             }else{
3729                 $string = get_string('blockname', 'block_'.basename($component));
3730             }
3731         break;
3733         default:
3734             error ('This is an unknown context $contextlevel (' . $contextlevel . ') in get_component_string!');
3735         return false;
3737     }
3738     return $string;
3741 /**
3742  * Gets the list of roles assigned to this context and up (parents)
3743  * @param object $context
3744  * @param view - set to true when roles are pulled for display only
3745  *               this is so that we can filter roles with no visible
3746  *               assignment, for example, you might want to "hide" all
3747  *               course creators when browsing the course participants
3748  *               list.
3749  * @return array
3750  */
3751 function get_roles_used_in_context($context, $view = false) {
3752     global $DB;
3754     // filter for roles with all hidden assignments
3755     // no need to return when only pulling roles for reviewing
3756     // e.g. participants page.
3757     $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))? ' AND ra.hidden = 0 ':'';
3758     $contextlist = get_related_contexts_string($context);
3760     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
3761               FROM {role_assignments} ra, {role} r
3762              WHERE r.id = ra.roleid
3763                    AND ra.contextid $contextlist
3764                    $hiddensql
3765           ORDER BY r.sortorder ASC";
3767     return $DB->get_records_sql($sql);
3770 /**
3771  * This function is used to print roles column in user profile page.
3772  * @param int userid
3773  * @param object context
3774  * @return string
3775  */
3776 function get_user_roles_in_context($userid, $context, $view=true){
3777     global $CFG, $DB,$USER;
3779     $rolestring = '';
3780     $sql = "SELECT *
3781             FROM {role_assignments} ra, {role} r
3782            WHERE ra.userid = ? and ra.contextid = ? and ra.roleid = r.id";
3783     $params = array($userid, $context->id);
3784     $rolenames = array();
3785     if ($roles = $DB->get_records_sql($sql, $params)) {
3786         foreach ($roles as $userrole) {
3787             // MDL-12544, if we are in view mode and current user has no capability to view hidden assignment, skip it
3788             if ($userrole->hidden && $view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
3789                 continue;
3790             }
3791             $rolenames[$userrole->roleid] = $userrole->name;
3792         }
3794         $rolenames = role_fix_names($rolenames, $context);   // Substitute aliases
3796         foreach ($rolenames as $roleid => $rolename) {
3797             $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
3798         }
3799         $rolestring = implode(',', $rolenames);
3800     }
3801     return $rolestring;
3805 /**
3806  * Checks if a user can override capabilities of a particular role in this context
3807  * @param object $context
3808  * @param int targetroleid - the id of the role you want to override
3809  * @return boolean
3810  */
3811 function user_can_override($context, $targetroleid) {
3813 // TODO: not needed anymore, remove in 2.0
3815     global $DB;
3816     // first check if user has override capability
3817     // if not return false;
3818     if (!has_capability('moodle/role:override', $context)) {
3819         return false;
3820     }
3821     // pull out all active roles of this user from this context(or above)
3822     if ($userroles = get_user_roles($context)) {
3823         foreach ($userroles as $userrole) {
3824             // if any in the role_allow_override table, then it's ok
3825             if ($DB->get_record('role_allow_override', array('roleid'=>$userrole->roleid, 'allowoverride'=>$targetroleid))) {
3826                 return true;
3827             }
3828         }
3829     }
3831     return false;
3835 /**
3836  * Checks if a user can assign users to a particular role in this context
3837  * @param object $context
3838  * @param int targetroleid - the id of the role you want to assign users to
3839  * @return boolean
3840  */
3841 function user_can_assign($context, $targetroleid) {
3842     global $DB;
3844     // first check if user has override capability
3845     // if not return false;
3846     if (!has_capability('moodle/role:assign', $context)) {
3847         return false;
3848     }
3849     // pull out all active roles of this user from this context(or above)
3850     if ($userroles = get_user_roles($context)) {
3851         foreach ($userroles as $userrole) {
3852             // if any in the role_allow_override table, then it's ok
3853             if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
3854                 return true;
3855             }
3856         }
3857     }
3859     return false;
3862 /**
3863  * Returns all site roles in correct sort order.
3864  * @return array
3865  */
3866 function get_all_roles() {
3867     global $DB;
3868     return $DB->get_records('role', null, 'sortorder ASC');
3871 /**
3872  * gets all the user roles assigned in this context, or higher contexts
3873  * this is mainly used when checking if a user can assign a role, or overriding a role
3874  * i.e. we need to know what this user holds, in order to verify against allow_assign and
3875  * allow_override tables
3876  * @param object $context
3877  * @param int $userid
3878  * @param view - set to true when roles are pulled for display only
3879  *               this is so that we can filter roles with no visible
3880  *               assignment, for example, you might want to "hide" all
3881  *               course creators when browsing the course participants
3882  *               list.
3883  * @return array
3884  */
3885 function get_user_roles($context, $userid=0, $checkparentcontexts=true, $order='c.contextlevel DESC, r.sortorder ASC', $view=false) {
3886     global $USER, $DB;
3888     if (empty($userid)) {
3889         if (empty($USER->id)) {
3890             return array();
3891         }
3892         $userid = $USER->id;
3893     }
3894     // set up hidden sql
3895     $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context)) ? "AND ra.hidden = 0" : "";
3897     if ($checkparentcontexts) {
3898         $contextids = get_parent_contexts($context);
3899     } else {
3900         $contextids = array();
3901     }
3902     $contextids[] = $context->id;
3904     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
3906     array_unshift($params, $userid);
3908     $sql = "SELECT ra.*, r.name, r.shortname
3909               FROM {role_assignments} ra, {role} r, {context} c
3910              WHERE ra.userid = ?
3911                    AND ra.roleid = r.id
3912                    AND ra.contextid = c.id
3913                    AND ra.contextid $contextids
3914                    $hiddensql
3915           ORDER BY $order";
3917     return $DB->get_records_sql($sql ,$params);
3920 /**
3921  * Creates a record in the allow_override table
3922  * @param int sroleid - source roleid
3923  * @param int troleid - target roleid
3924  * @return int - id or false
3925  */
3926 function allow_override($sroleid, $troleid) {
3927     global $DB;
3929     $record = new object();
3930     $record->roleid        = $sroleid;
3931     $record->allowoverride = $troleid;
3932     return $DB->insert_record('role_allow_override', $record);
3935 /**
3936  * Creates a record in the allow_assign table
3937  * @param int sroleid - source roleid
3938  * @param int troleid - target roleid
3939  * @return int - id or false
3940  */
3941 function allow_assign($sroleid, $troleid) {
3942     global $DB;
3944     $record = new object;
3945     $record->roleid      = $sroleid;
3946     $record->allowassign = $troleid;
3947     return $DB->insert_record('role_allow_assign', $record);
3950 /**
3951  * Gets a list of roles that this user can assign in this context
3952  * @param object $context
3953  * @param string $field
3954  * @param int $rolenamedisplay
3955  * @return array
3956  */
3957 function get_assignable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS) {
3958     global $USER, $DB;
3960     if (!has_capability('moodle/role:assign', $context)) {
3961         return array();
3962     } 
3964     $parents = get_parent_contexts($context);
3965     $parents[] = $context->id;
3966     $contexts = implode(',' , $parents);
3968     if (!$roles = $DB->get_records_sql("SELECT DISTINCT r.*
3969                                           FROM {role} r,
3970                                                {role_assignments} ra,
3971                                                {role_allow_assign} raa
3972                                          WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3973                                                AND raa.roleid = ra.roleid AND r.id = raa.allowassign
3974                                       ORDER BY r.sortorder ASC", array('userid'=>$USER->id))) {
3975         return array();
3976     }
3978     foreach ($roles as $role) {
3979         $roles[$role->id] = $role->$field;
3980     }
3982     return role_fix_names($roles, $context, $rolenamedisplay);
3985 /**
3986  * Gets a list of roles that this user can assign in this context, for the switchrole menu
3987  *
3988  * @param object $context
3989  * @param string $field
3990  * @param int $rolenamedisplay
3991  * @return array
3992  */
3993 function get_assignable_roles_for_switchrole($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS) {
3994     global $USER, $DB;
3996     if (!has_capability('moodle/role:assign', $context)) {
3997         return array();
3998     } 
4000     $parents = get_parent_contexts($context);
4001     $parents[] = $context->id;
4002     $contexts = implode(',' , $parents);
4004     if (!$roles = $DB->get_records_sql("SELECT DISTINCT r.*
4005                                           FROM {role} r,
4006                                                {role_assignments} ra,
4007                                                {role_allow_assign} raa,
4008                                                {role_capabilities} rc
4009                                          WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
4010                                                AND raa.roleid = ra.roleid AND r.id = raa.allowassign
4011                                                AND r.id = rc.roleid AND rc.capability = :viewcap AND rc.capability <> :anythingcap
4012                                       ORDER BY r.sortorder ASC", array('userid'=>$USER->id, 'viewcap'=>'moodle/course:view', 'anythingcap'=>'moodle/site:doanything'))) {
4013         return array();
4014     }
4016     foreach ($roles as $role) {
4017         $roles[$role->id] = $role->$field;
4018     }
4020     return role_fix_names($roles, $context, $rolenamedisplay);
4023 /**
4024  * Gets a list of roles that this user can override in this context
4025  * @param object $context
4026  * @param string $field
4027  * @param int $rolenamedisplay
4028  * @return array
4029  */
4030 function get_overridable_roles($context, $field='name', $rolenamedisplay=ROLENAME_ALIAS) {
4031     global $USER, $DB;
4033     if (!has_capability('moodle/role:override', $context) and !has_capability('moodle/role:safeoverride', $context)) {
4034         return array();
4035     } 
4037     $parents = get_parent_contexts($context);
4038     $parents[] = $context->id;
4039     $contexts = implode(',' , $parents);
4041     if (!$roles = $DB->get_records_sql("SELECT DISTINCT r.*
4042                                           FROM {role} r,
4043                                                {role_assignments} ra,
4044                                                {role_allow_override} rao 
4045                                          WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
4046                                                AND rao.roleid = ra.roleid AND r.id = rao.allowoverride
4047                                       ORDER BY r.sortorder ASC", array('userid'=>$USER->id))) {
4048         return array();
4049     }
4051     foreach ($roles as $role) {
4052         $roles[$role->id] = $role->$field;
4053     }
4055     return role_fix_names($roles, $context, $rolenamedisplay);
4058 /**
4059  *  Returns a role object that is the default role for new enrolments
4060  *  in a given course
4061  *
4062  *  @param object $course
4063  *  @return object $role
4064  */
4065 function get_default_course_role($course) {
4066     global $DB, $CFG;
4068 /// First let's take the default role the course may have
4069     if (!empty($course->defaultrole)) {
4070         if ($role = $DB->get_record('role', array('id'=>$course->defaultrole))) {
4071             return $role;
4072         }
4073     }
4075 /// Otherwise the site setting should tell us
4076     if ($CFG->defaultcourseroleid) {
4077         if ($role = $DB->get_record('role', array('id'=>$CFG->defaultcourseroleid))) {
4078             return $role;
4079         }
4080     }
4082 /// It's unlikely we'll get here, but just in case, try and find a student role
4083     if ($studentroles = get_roles_with_capability('moodle/legacy:student', CAP_ALLOW)) {
4084         return array_shift($studentroles);   /// Take the first one
4085     }
4087     return NULL;
4091 /**
4092  * Who has this capability in this context?
4093  *
4094  * This can be a very expensive call - use sparingly and keep
4095  * the results if you are going to need them again soon.
4096  *
4097  * Note if $fields is empty this function attempts to get u.*
4098  * which can get rather large - and has a serious perf impact
4099  * on some DBs.
4100  *
4101  * @param $context - object
4102  * @param $capability - string capability
4103  * @param $fields - fields to be pulled
4104  * @param $sort - the sort order
4105  * @param $limitfrom - number of records to skip (offset)
4106  * @param $limitnum - number of records to fetch
4107  * @param $groups - single group or array of groups - only return
4108  *               users who are in one of these group(s).
4109  * @param $exceptions - list of users to exclude, comma separated or array
4110  * @param view - set to true when roles are pulled for display only
4111  *               this is so that we can filter roles with no visible
4112  *               assignment, for example, you might want to "hide" all
4113  *               course creators when browsing the course participants
4114  *               list.
4115  * @param boolean $useviewallgroups if $groups is set the return users who
4116  *               have capability both $capability and moodle/site:accessallgroups
4117  *               in this context, as well as users who have $capability and who are
4118  *               in $groups.
4119  */
4120 function get_users_by_capability($context, $capability, $fields='', $sort='',
4121         $limitfrom='', $limitnum='', $groups='', $exceptions='', $doanything=true,
4122         $view=false, $useviewallgroups=false) {
4123     global $CFG, $DB;
4125     $ctxids = substr($context->path, 1); // kill leading slash
4126     $ctxids = str_replace('/', ',', $ctxids);
4128     // Context is the frontpage
4129     $isfrontpage = false;
4130     $iscoursepage = false; // coursepage other than fp
4131     if ($context->contextlevel == CONTEXT_COURSE) {
4132         if ($context->instanceid == SITEID) {
4133             $isfrontpage = true;
4134         } else {
4135             $iscoursepage = true;
4136         }
4137     }
4139     // What roles/rolecaps are interesting?
4140     $caps = array($capability);
4141     if ($doanything === true) {
4142         $caps[] = 'moodle/site:doanything';
4143         $doanything_join='';
4144         $doanything_cond='';
4146     } else {
4147         // This is an outer join against
4148         // admin-ish roleids. Any row that succeeds
4149         // in JOINing here ends up removed from
4150         // the resultset. This means we remove
4151         // rolecaps from roles that also have
4152         // 'doanything' capabilities.
4153         $doanything_join="LEFT OUTER JOIN (
4154                               SELECT DISTINCT rc.roleid
4155                               FROM {role_capabilities} rc
4156                               WHERE rc.capability=:capany
4157                                     AND rc.permission=".CAP_ALLOW."
4158                                     AND rc.contextid IN ($ctxids)
4159                           ) dar
4160                              ON rc.roleid=dar.roleid";
4161         $doanything_cond="AND dar.roleid IS NULL";
4162     }
4164     // fetch all capability records - we'll walk several
4165     // times over them, and should be a small set
4167     $negperm = false; // has any negative (<0) permission?
4168     $roleids = array();
4170     list($caps, $params) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap0');
4171     $params['capany'] = 'moodle/site:doanything';
4173     $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability,
4174                    ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel
4175               FROM {role_capabilities} rc
4176               JOIN {context} ctx on rc.contextid = ctx.id
4177   $doanything_join
4178              WHERE rc.capability $caps AND ctx.id IN ($ctxids)
4179                    $doanything_cond
4180           ORDER BY rc.roleid ASC, ctx.depth ASC";
4182     if ($capdefs = $DB->get_records_sql($sql, $params)) {
4183         foreach ($capdefs AS $rcid=>$rc) {
4184             $roleids[] = (int)$rc->roleid;
4185             if ($rc->permission < 0) {
4186                 $negperm = true;
4187             }
4188         }
4189     }
4191     $roleids = array_unique($roleids);
4193     if (count($roleids)===0) { // noone here!
4194         return array();
4195     }
4197     // is the default role interesting? does it have
4198     // a relevant rolecap? (we use this a lot later)
4199     if (in_array((int)$CFG->defaultuserroleid, $roleids, true)) {
4200         $defaultroleinteresting = true;
4201     } else {
4202         $defaultroleinteresting = false;