7a119be15fe38de72985534660ac772bbfb82b32
[moodle.git] / lib / accesslib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * This file contains functions for managing user access
20  *
21  * <b>Public API vs internals</b>
22  *
23  * General users probably only care about
24  *
25  * Context handling
26  * - get_context_instance()
27  * - get_context_instance_by_id()
28  * - get_parent_contexts()
29  * - get_child_contexts()
30  *
31  * Whether the user can do something...
32  * - has_capability()
33  * - has_any_capability()
34  * - has_all_capabilities()
35  * - require_capability()
36  * - require_login() (from moodlelib)
37  *
38  * What courses has this user access to?
39  * - get_user_courses_bycap()
40  *
41  * What users can do X in this context?
42  * - get_users_by_capability()
43  *
44  * Enrol/unenrol
45  * - enrol_into_course()
46  * - role_assign()/role_unassign()
47  *
48  *
49  * Advanced use
50  * - load_all_capabilities()
51  * - reload_all_capabilities()
52  * - has_capability_in_accessdata()
53  * - is_siteadmin()
54  * - get_user_access_sitewide()
55  * - load_subcontext()
56  * - get_role_access_bycontext()
57  *
58  * <b>Name conventions</b>
59  *
60  * "ctx" means context
61  *
62  * <b>accessdata</b>
63  *
64  * Access control data is held in the "accessdata" array
65  * which - for the logged-in user, will be in $USER->access
66  *
67  * For other users can be generated and passed around (but may also be cached
68  * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser.
69  *
70  * $accessdata is a multidimensional array, holding
71  * role assignments (RAs), role-capabilities-perm sets
72  * (role defs) and a list of courses we have loaded
73  * data for.
74  *
75  * Things are keyed on "contextpaths" (the path field of
76  * the context table) for fast walking up/down the tree.
77  * <code>
78  * $accessdata[ra][$contextpath]= array($roleid)
79  *                [$contextpath]= array($roleid)
80  *                [$contextpath]= array($roleid)
81  * </code>
82  *
83  * Role definitions are stored like this
84  * (no cap merge is done - so it's compact)
85  *
86  * <code>
87  * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
88  *                                        [mod/forum:editallpost] = -1
89  *                                        [mod/forum:startdiscussion] = -1000
90  * </code>
91  *
92  * See how has_capability_in_accessdata() walks up/down the tree.
93  *
94  * Normally - specially for the logged-in user, we only load
95  * rdef and ra down to the course level, but not below. This
96  * keeps accessdata small and compact. Below-the-course ra/rdef
97  * are loaded as needed. We keep track of which courses we
98  * have loaded ra/rdef in
99  * <code>
100  * $accessdata[loaded] = array($contextpath, $contextpath)
101  * </code>
102  *
103  * <b>Stale accessdata</b>
104  *
105  * For the logged-in user, accessdata is long-lived.
106  *
107  * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
108  * context paths affected by changes. Any check at-or-below
109  * a dirty context will trigger a transparent reload of accessdata.
110  *
111  * Changes at the system level will force the reload for everyone.
112  *
113  * <b>Default role caps</b>
114  * The default role assignment is not in the DB, so we
115  * add it manually to accessdata.
116  *
117  * This means that functions that work directly off the
118  * DB need to ensure that the default role caps
119  * are dealt with appropriately.
120  *
121  * @package    core
122  * @subpackage role
123  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
124  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
125  */
127 defined('MOODLE_INTERNAL') || die();
129 /** permission definitions */
130 define('CAP_INHERIT', 0);
131 /** permission definitions */
132 define('CAP_ALLOW', 1);
133 /** permission definitions */
134 define('CAP_PREVENT', -1);
135 /** permission definitions */
136 define('CAP_PROHIBIT', -1000);
138 /** context definitions */
139 define('CONTEXT_SYSTEM', 10);
140 /** context definitions */
141 define('CONTEXT_USER', 30);
142 /** context definitions */
143 define('CONTEXT_COURSECAT', 40);
144 /** context definitions */
145 define('CONTEXT_COURSE', 50);
146 /** context definitions */
147 define('CONTEXT_MODULE', 70);
148 /** context definitions */
149 define('CONTEXT_BLOCK', 80);
151 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
152 define('RISK_MANAGETRUST', 0x0001);
153 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
154 define('RISK_CONFIG',      0x0002);
155 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
156 define('RISK_XSS',         0x0004);
157 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
158 define('RISK_PERSONAL',    0x0008);
159 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
160 define('RISK_SPAM',        0x0010);
161 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
162 define('RISK_DATALOSS',    0x0020);
164 /** rolename displays - the name as defined in the role definition */
165 define('ROLENAME_ORIGINAL', 0);
166 /** rolename displays - the name as defined by a role alias */
167 define('ROLENAME_ALIAS', 1);
168 /** rolename displays - Both, like this:  Role alias (Original)*/
169 define('ROLENAME_BOTH', 2);
170 /** rolename displays - the name as defined in the role definition and the shortname in brackets*/
171 define('ROLENAME_ORIGINALANDSHORT', 3);
172 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing*/
173 define('ROLENAME_ALIAS_RAW', 4);
174 /** rolename displays - the name is simply short role name*/
175 define('ROLENAME_SHORT', 5);
177 /**
178  * Internal class provides a cache of context information. The cache is
179  * restricted in size.
180  *
181  * This cache should NOT be used outside accesslib.php!
182  *
183  * @private
184  * @author Sam Marshall
185  */
186 class context_cache {
187     private $contextsbyid;
188     private $contexts;
189     private $count;
191     /**
192      * @var int Maximum number of contexts that will be cached.
193      */
194     const MAX_SIZE = 2500;
195     /**
196      * @var int Once contexts reach maximum number, this many will be removed from cache.
197      */
198     const REDUCE_SIZE = 1000;
200     /**
201      * Initialises (empty)
202      */
203     public function __construct() {
204         $this->reset();
205     }
207     /**
208      * Resets the cache to remove all data.
209      */
210     public function reset() {
211         $this->contexts     = array();
212         $this->contextsbyid = array();
213         $this->count        = 0;
214     }
216     /**
217      * Adds a context to the cache. If the cache is full, discards a batch of
218      * older entries.
219      * @param stdClass $context New context to add
220      */
221     public function add(stdClass $context) {
222         if ($this->count >= self::MAX_SIZE) {
223             for ($i=0; $i<self::REDUCE_SIZE; $i++) {
224                 if ($first = reset($this->contextsbyid)) {
225                     unset($this->contextsbyid[$first->id]);
226                     unset($this->contexts[$first->contextlevel][$first->instanceid]);
227                 }
228             }
229             $this->count -= self::REDUCE_SIZE;
230             if ($this->count < 0) {
231                 // most probably caused by the drift, the reset() above
232                 // might have returned false because there might not be any more elements
233                 $this->count = 0;
234             }
235         }
237         $this->contexts[$context->contextlevel][$context->instanceid] = $context;
238         $this->contextsbyid[$context->id] = $context;
240         // Note the count may get out of synch slightly if you cache a context
241         // that is already cached, but it doesn't really matter much and I
242         // didn't think it was worth the performance hit.
243         $this->count++;
244     }
246     /**
247      * Removes a context from the cache.
248      * @param stdClass $context Context object to remove (must include fields
249      *   ->id, ->contextlevel, ->instanceid at least)
250      */
251     public function remove(stdClass $context) {
252         unset($this->contexts[$context->contextlevel][$context->instanceid]);
253         unset($this->contextsbyid[$context->id]);
255         // Again the count may get a bit out of synch if you remove things
256         // that don't exist
257         $this->count--;
259         if ($this->count < 0) {
260             $this->count = 0;
261         }
262     }
264     /**
265      * Gets a context from the cache.
266      * @param int $contextlevel Context level
267      * @param int $instance Instance ID
268      * @return stdClass|bool Context or false if not in cache
269      */
270     public function get($contextlevel, $instance) {
271         if (isset($this->contexts[$contextlevel][$instance])) {
272             return $this->contexts[$contextlevel][$instance];
273         }
274         return false;
275     }
277     /**
278      * Gets a context from the cache based on its id.
279      * @param int $id Context ID
280      * @return stdClass|bool Context or false if not in cache
281      */
282     public function get_by_id($id) {
283         if (isset($this->contextsbyid[$id])) {
284             return $this->contextsbyid[$id];
285         }
286         return false;
287     }
289     /**
290      * @return int Count of contexts in cache (approximately)
291      */
292     public function get_approx_count() {
293         return $this->count;
294     }
297 /**
298  * Although this looks like a global variable, it isn't really.
299  *
300  * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
301  * It is used to cache various bits of data between function calls for performance reasons.
302  * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
303  * as methods of a class, instead of functions.
304  *
305  * @global stdClass $ACCESSLIB_PRIVATE
306  * @name $ACCESSLIB_PRIVATE
307  */
308 global $ACCESSLIB_PRIVATE;
309 $ACCESSLIB_PRIVATE = new stdClass();
310 $ACCESSLIB_PRIVATE->contexcache      = new context_cache();
311 $ACCESSLIB_PRIVATE->systemcontext    = null; // Used in get_system_context
312 $ACCESSLIB_PRIVATE->dirtycontexts    = null; // Dirty contexts cache
313 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER
314 $ACCESSLIB_PRIVATE->roledefinitions  = array(); // role definitions cache - helps a lot with mem usage in cron
315 $ACCESSLIB_PRIVATE->croncache        = array(); // Used in get_role_access
316 $ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts.
317 $ACCESSLIB_PRIVATE->capabilities     = null; // detailed information about the capabilities
319 /**
320  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
321  *
322  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
323  * accesslib's private caches. You need to do this before setting up test data,
324  * and also at the end of the tests.
325  */
326 function accesslib_clear_all_caches_for_unit_testing() {
327     global $UNITTEST, $USER, $ACCESSLIB_PRIVATE;
328     if (empty($UNITTEST->running)) {
329         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
330     }
331     $ACCESSLIB_PRIVATE->contexcache      = new context_cache();
332     $ACCESSLIB_PRIVATE->systemcontext    = null;
333     $ACCESSLIB_PRIVATE->dirtycontexts    = null;
334     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
335     $ACCESSLIB_PRIVATE->roledefinitions  = array();
336     $ACCESSLIB_PRIVATE->croncache        = array();
337     $ACCESSLIB_PRIVATE->preloadedcourses = array();
338     $ACCESSLIB_PRIVATE->capabilities     = null;
340     unset($USER->access);
343 /**
344  * This is really slow!!! do not use above course context level
345  *
346  * @param int $roleid
347  * @param object $context
348  * @return array
349  */
350 function get_role_context_caps($roleid, $context) {
351     global $DB;
353     //this is really slow!!!! - do not use above course context level!
354     $result = array();
355     $result[$context->id] = array();
357     // first emulate the parent context capabilities merging into context
358     $searchcontexts = array_reverse(get_parent_contexts($context));
359     array_push($searchcontexts, $context->id);
360     foreach ($searchcontexts as $cid) {
361         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
362             foreach ($capabilities as $cap) {
363                 if (!array_key_exists($cap->capability, $result[$context->id])) {
364                     $result[$context->id][$cap->capability] = 0;
365                 }
366                 $result[$context->id][$cap->capability] += $cap->permission;
367             }
368         }
369     }
371     // now go through the contexts bellow given context
372     $searchcontexts = array_keys(get_child_contexts($context));
373     foreach ($searchcontexts as $cid) {
374         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
375             foreach ($capabilities as $cap) {
376                 if (!array_key_exists($cap->contextid, $result)) {
377                     $result[$cap->contextid] = array();
378                 }
379                 $result[$cap->contextid][$cap->capability] = $cap->permission;
380             }
381         }
382     }
384     return $result;
387 /**
388  * Gets the accessdata for role "sitewide" (system down to course)
389  *
390  * @param int $roleid
391  * @param array $accessdata defaults to null
392  * @return array
393  */
394 function get_role_access($roleid, $accessdata = null) {
395     global $CFG, $DB;
397     /* Get it in 1 cheap DB query...
398      * - relevant role caps at the root and down
399      *   to the course level - but not below
400      */
401     if (is_null($accessdata)) {
402         $accessdata           = array(); // named list
403         $accessdata['ra']     = array();
404         $accessdata['rdef']   = array();
405         $accessdata['loaded'] = array();
406     }
408     //
409     // Overrides for the role IN ANY CONTEXTS
410     // down to COURSE - not below -
411     //
412     $sql = "SELECT ctx.path,
413                    rc.capability, rc.permission
414               FROM {context} ctx
415               JOIN {role_capabilities} rc
416                    ON rc.contextid=ctx.id
417              WHERE rc.roleid = ?
418                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
419           ORDER BY ctx.depth, ctx.path";
420     $params = array($roleid);
422     // we need extra caching in CLI scripts and cron
423     if (CLI_SCRIPT) {
424         global $ACCESSLIB_PRIVATE;
426         if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) {
427             $ACCESSLIB_PRIVATE->croncache[$roleid] = array();
428             if ($rs = $DB->get_recordset_sql($sql, $params)) {
429                 foreach ($rs as $rd) {
430                     $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd;
431                 }
432                 $rs->close();
433             }
434         }
436         foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) {
437             $k = "{$rd->path}:{$roleid}";
438             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
439         }
441     } else {
442         if ($rs = $DB->get_recordset_sql($sql, $params)) {
443             foreach ($rs as $rd) {
444                 $k = "{$rd->path}:{$roleid}";
445                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
446             }
447             unset($rd);
448             $rs->close();
449         }
450     }
452     return $accessdata;
455 /**
456  * Gets the accessdata for role "sitewide" (system down to course)
457  *
458  * @param int $roleid
459  * @param array $accessdata defaults to null
460  * @return array
461  */
462 function get_default_frontpage_role_access($roleid, $accessdata = null) {
464     global $CFG, $DB;
466     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
467     $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
469     //
470     // Overrides for the role in any contexts related to the course
471     //
472     $sql = "SELECT ctx.path,
473                    rc.capability, rc.permission
474               FROM {context} ctx
475               JOIN {role_capabilities} rc
476                    ON rc.contextid=ctx.id
477              WHERE rc.roleid = ?
478                    AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?)
479                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
480           ORDER BY ctx.depth, ctx.path";
481     $params = array($roleid, "$base/%");
483     if ($rs = $DB->get_recordset_sql($sql, $params)) {
484         foreach ($rs as $rd) {
485             $k = "{$rd->path}:{$roleid}";
486             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
487         }
488         unset($rd);
489         $rs->close();
490     }
492     return $accessdata;
496 /**
497  * Get the default guest role
498  *
499  * @return stdClass role
500  */
501 function get_guest_role() {
502     global $CFG, $DB;
504     if (empty($CFG->guestroleid)) {
505         if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
506             $guestrole = array_shift($roles);   // Pick the first one
507             set_config('guestroleid', $guestrole->id);
508             return $guestrole;
509         } else {
510             debugging('Can not find any guest role!');
511             return false;
512         }
513     } else {
514         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
515             return $guestrole;
516         } else {
517             //somebody is messing with guest roles, remove incorrect setting and try to find a new one
518             set_config('guestroleid', '');
519             return get_guest_role();
520         }
521     }
524 /**
525  * Check whether a user has a particular capability in a given context.
526  *
527  * For example::
528  *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
529  *      has_capability('mod/forum:replypost',$context)
530  *
531  * By default checks the capabilities of the current user, but you can pass a
532  * different userid. By default will return true for admin users, but you can override that with the fourth argument.
533  *
534  * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
535  * or capabilities with XSS, config or data loss risks.
536  *
537  * @param string $capability the name of the capability to check. For example mod/forum:view
538  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
539  * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
540  * @param boolean $doanything If false, ignores effect of admin role assignment
541  * @return boolean true if the user has this capability. Otherwise false.
542  */
543 function has_capability($capability, $context, $user = null, $doanything = true) {
544     global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
546     if (during_initial_install()) {
547         if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") {
548             // we are in an installer - roles can not work yet
549             return true;
550         } else {
551             return false;
552         }
553     }
555     if (strpos($capability, 'moodle/legacy:') === 0) {
556         throw new coding_exception('Legacy capabilities can not be used any more!');
557     }
559     // the original $CONTEXT here was hiding serious errors
560     // for security reasons do not reuse previous context
561     if (empty($context)) {
562         debugging('Incorrect context specified');
563         return false;
564     }
565     if (!is_bool($doanything)) {
566         throw new coding_exception('Capability parameter "doanything" is wierd ("'.$doanything.'"). This has to be fixed in code.');
567     }
569     // make sure there is a real user specified
570     if ($user === null) {
571         $userid = !empty($USER->id) ? $USER->id : 0;
572     } else {
573         $userid = !empty($user->id) ? $user->id : $user;
574     }
576     // capability must exist
577     if (!$capinfo = get_capability_info($capability)) {
578         debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
579         return false;
580     }
581     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
582     if (($capinfo->captype === 'write') or ((int)$capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
583         if (isguestuser($userid) or $userid == 0) {
584             return false;
585         }
586     }
588     if (is_null($context->path) or $context->depth == 0) {
589         //this should not happen
590         $contexts = array(SYSCONTEXTID, $context->id);
591         $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
592         debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
594     } else {
595         $contexts = explode('/', $context->path);
596         array_shift($contexts);
597     }
599     if (CLI_SCRIPT && !isset($USER->access)) {
600         // In cron, some modules setup a 'fake' $USER,
601         // ensure we load the appropriate accessdata.
602         if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
603             $ACCESSLIB_PRIVATE->dirtycontexts = null; //load fresh dirty contexts
604         } else {
605             load_user_accessdata($userid);
606             $ACCESSLIB_PRIVATE->dirtycontexts = array();
607         }
608         $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
610     } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) {
611         // caps not loaded yet - better to load them to keep BC with 1.8
612         // not-logged-in user or $USER object set up manually first time here
613         load_all_capabilities();
614         $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now
615         $ACCESSLIB_PRIVATE->roledefinitions = array();
616     }
618     // Load dirty contexts list if needed
619     if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
620         if (isset($USER->access['time'])) {
621             $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
622         }
623         else {
624             $ACCESSLIB_PRIVATE->dirtycontexts = array();
625         }
626     }
628     // Careful check for staleness...
629     if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
630         // reload all capabilities - preserving loginas, roleswitches, etc
631         // and then cleanup any marks of dirtyness... at least from our short
632         // term memory! :-)
633         $ACCESSLIB_PRIVATE->accessdatabyuser = array();
634         $ACCESSLIB_PRIVATE->roledefinitions = array();
636         if (CLI_SCRIPT) {
637             load_user_accessdata($userid);
638             $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
639             $ACCESSLIB_PRIVATE->dirtycontexts = array();
641         } else {
642             reload_all_capabilities();
643         }
644     }
646     // Find out if user is admin - it is not possible to override the doanything in any way
647     // and it is not possible to switch to admin role either.
648     if ($doanything) {
649         if (is_siteadmin($userid)) {
650             if ($userid != $USER->id) {
651                 return true;
652             }
653             // make sure switchrole is not used in this context
654             if (empty($USER->access['rsw'])) {
655                 return true;
656             }
657             $parts = explode('/', trim($context->path, '/'));
658             $path = '';
659             $switched = false;
660             foreach ($parts as $part) {
661                 $path .= '/' . $part;
662                 if (!empty($USER->access['rsw'][$path])) {
663                     $switched = true;
664                     break;
665                 }
666             }
667             if (!$switched) {
668                 return true;
669             }
670             //ok, admin switched role in this context, let's use normal access control rules
671         }
672     }
674     // divulge how many times we are called
675     //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
677     if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid
678         //
679         // For the logged in user, we have $USER->access
680         // which will have all RAs and caps preloaded for
681         // course and above contexts.
682         //
683         // Contexts below courses && contexts that do not
684         // hang from courses are loaded into $USER->access
685         // on demand, and listed in $USER->access[loaded]
686         //
687         if ($context->contextlevel <= CONTEXT_COURSE) {
688             // Course and above are always preloaded
689             return has_capability_in_accessdata($capability, $context, $USER->access);
690         }
691         // Load accessdata for below-the-course contexts
692         if (!path_inaccessdata($context->path,$USER->access)) {
693             // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
694             // $bt = debug_backtrace();
695             // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
696             load_subcontext($USER->id, $context, $USER->access);
697         }
698         return has_capability_in_accessdata($capability, $context, $USER->access);
699     }
701     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
702         load_user_accessdata($userid);
703     }
705     if ($context->contextlevel <= CONTEXT_COURSE) {
706         // Course and above are always preloaded
707         return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
708     }
709     // Load accessdata for below-the-course contexts as needed
710     if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
711         // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
712         // $bt = debug_backtrace();
713         // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
714         load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
715     }
716     return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
719 /**
720  * Check if the user has any one of several capabilities from a list.
721  *
722  * This is just a utility method that calls has_capability in a loop. Try to put
723  * the capabilities that most users are likely to have first in the list for best
724  * performance.
725  *
726  * There are probably tricks that could be done to improve the performance here, for example,
727  * check the capabilities that are already cached first.
728  *
729  * @see has_capability()
730  * @param array $capabilities an array of capability names.
731  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
732  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
733  * @param boolean $doanything If false, ignore effect of admin role assignment
734  * @return boolean true if the user has any of these capabilities. Otherwise false.
735  */
736 function has_any_capability($capabilities, $context, $userid = null, $doanything = true) {
737     if (!is_array($capabilities)) {
738         debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array');
739         return false;
740     }
741     foreach ($capabilities as $capability) {
742         if (has_capability($capability, $context, $userid, $doanything)) {
743             return true;
744         }
745     }
746     return false;
749 /**
750  * Check if the user has all the capabilities in a list.
751  *
752  * This is just a utility method that calls has_capability in a loop. Try to put
753  * the capabilities that fewest users are likely to have first in the list for best
754  * performance.
755  *
756  * There are probably tricks that could be done to improve the performance here, for example,
757  * check the capabilities that are already cached first.
758  *
759  * @see has_capability()
760  * @param array $capabilities an array of capability names.
761  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
762  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
763  * @param boolean $doanything If false, ignore effect of admin role assignment
764  * @return boolean true if the user has all of these capabilities. Otherwise false.
765  */
766 function has_all_capabilities($capabilities, $context, $userid = null, $doanything = true) {
767     if (!is_array($capabilities)) {
768         debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
769         return false;
770     }
771     foreach ($capabilities as $capability) {
772         if (!has_capability($capability, $context, $userid, $doanything)) {
773             return false;
774         }
775     }
776     return true;
779 /**
780  * Check if the user is an admin at the site level.
781  *
782  * Please note that use of proper capabilities is always encouraged,
783  * this function is supposed to be used from core or for temporary hacks.
784  *
785  * @param   int|object  $user_or_id user id or user object
786  * @returns bool true if user is one of the administrators, false otherwise
787  */
788 function is_siteadmin($user_or_id = null) {
789     global $CFG, $USER;
791     if ($user_or_id === null) {
792         $user_or_id = $USER;
793     }
795     if (empty($user_or_id)) {
796         return false;
797     }
798     if (!empty($user_or_id->id)) {
799         // we support
800         $userid = $user_or_id->id;
801     } else {
802         $userid = $user_or_id;
803     }
805     $siteadmins = explode(',', $CFG->siteadmins);
806     return in_array($userid, $siteadmins);
809 /**
810  * Returns true if user has at least one role assign
811  * of 'coursecontact' role (is potentially listed in some course descriptions).
812  *
813  * @param $userid
814  * @return stdClass
815  */
816 function has_coursecontact_role($userid) {
817     global $DB, $CFG;
819     if (empty($CFG->coursecontact)) {
820         return false;
821     }
822     $sql = "SELECT 1
823               FROM {role_assignments}
824              WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
825     return $DB->record_exists_sql($sql, array('userid'=>$userid));
828 /**
829  * @param string $path
830  * @return string
831  */
832 function get_course_from_path($path) {
833     // assume that nothing is more than 1 course deep
834     if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
835         return $matches[1];
836     }
837     return false;
840 /**
841  * @param string $path
842  * @param array $accessdata
843  * @return bool
844  */
845 function path_inaccessdata($path, $accessdata) {
846     if (empty($accessdata['loaded'])) {
847         return false;
848     }
850     // assume that contexts hang from sys or from a course
851     // this will only work well with stuff that hangs from a course
852     if (in_array($path, $accessdata['loaded'], true)) {
853             // error_log("found it!");
854         return true;
855     }
856     $base = '/' . SYSCONTEXTID;
857     while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
858         $path = $matches[1];
859         if ($path === $base) {
860             return false;
861         }
862         if (in_array($path, $accessdata['loaded'], true)) {
863             return true;
864         }
865     }
866     return false;
869 /**
870  * Does the user have a capability to do something?
871  *
872  * Walk the accessdata array and return true/false.
873  * Deals with prohibits, roleswitching, aggregating
874  * capabilities, etc.
875  *
876  * The main feature of here is being FAST and with no
877  * side effects.
878  *
879  * Notes:
880  *
881  * Switch Roles exits early
882  * ------------------------
883  * cap checks within a switchrole need to exit early
884  * in our bottom up processing so they don't "see" that
885  * there are real RAs that can do all sorts of things.
886  *
887  * Switch Role merges with default role
888  * ------------------------------------
889  * If you are a teacher in course X, you have at least
890  * teacher-in-X + defaultloggedinuser-sitewide. So in the
891  * course you'll have techer+defaultloggedinuser.
892  * We try to mimic that in switchrole.
893  *
894  * Permission evaluation
895  * ---------------------
896  * Originally there was an extremely complicated way
897  * to determine the user access that dealt with
898  * "locality" or role assignments and role overrides.
899  * Now we simply evaluate access for each role separately
900  * and then verify if user has at least one role with allow
901  * and at the same time no role with prohibit.
902  *
903  * @param string $capability
904  * @param object $context
905  * @param array $accessdata
906  * @return bool
907  */
908 function has_capability_in_accessdata($capability, $context, array $accessdata) {
909     global $CFG;
911     if (empty($context->id)) {
912         throw new coding_exception('Invalid context specified');
913     }
915     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
916     $contextids = explode('/', trim($context->path, '/'));
917     $paths = array($context->path);
918     while ($contextids) {
919         array_pop($contextids);
920         $paths[] = '/' . implode('/', $contextids);
921     }
922     unset($contextids);
924     $roles = array();
925     $switchedrole = false;
927     // Find out if role switched
928     if (!empty($accessdata['rsw'])) {
929         // From the bottom up...
930         foreach ($paths as $path) {
931             if (isset($accessdata['rsw'][$path])) {
932                 // Found a switchrole assignment - check for that role _plus_ the default user role
933                 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
934                 $switchedrole = true;
935                 break;
936             }
937         }
938     }
940     if (!$switchedrole) {
941         // get all users roles in this context and above
942         foreach ($paths as $path) {
943             if (isset($accessdata['ra'][$path])) {
944                 foreach ($accessdata['ra'][$path] as $roleid) {
945                     $roles[$roleid] = null;
946                 }
947             }
948         }
949     }
951     // Now find out what access is given to each role, going bottom-->up direction
952     foreach ($roles as $roleid => $ignored) {
953         foreach ($paths as $path) {
954             if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
955                 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
956                 if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) {
957                     $roles[$roleid] = $perm;
958                 }
959             }
960         }
961     }
962     // any CAP_PROHIBIT found means no permission for the user
963     if (array_search(CAP_PROHIBIT, $roles) !== false) {
964         return false;
965     }
967     // at least one CAP_ALLOW means the user has a permission
968     return (array_search(CAP_ALLOW, $roles) !== false);
971 /**
972  * @param object $context
973  * @param array $accessdata
974  * @return array
975  */
976 function aggregate_roles_from_accessdata($context, $accessdata) {
978     $path = $context->path;
980     // build $contexts as a list of "paths" of the current
981     // contexts and parents with the order top-to-bottom
982     $contexts = array($path);
983     while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
984         $path = $matches[1];
985         array_unshift($contexts, $path);
986     }
988     $cc = count($contexts);
990     $roles = array();
991     // From the bottom up...
992     for ($n=$cc-1; $n>=0; $n--) {
993         $ctxp = $contexts[$n];
994         if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
995             // Found assignments on this leaf
996             $addroles = $accessdata['ra'][$ctxp];
997             $roles    = array_merge($roles, $addroles);
998         }
999     }
1001     return array_unique($roles);
1004 /**
1005  * A convenience function that tests has_capability, and displays an error if
1006  * the user does not have that capability.
1007  *
1008  * NOTE before Moodle 2.0, this function attempted to make an appropriate
1009  * require_login call before checking the capability. This is no longer the case.
1010  * You must call require_login (or one of its variants) if you want to check the
1011  * user is logged in, before you call this function.
1012  *
1013  * @see has_capability()
1014  *
1015  * @param string $capability the name of the capability to check. For example mod/forum:view
1016  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
1017  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
1018  * @param bool $doanything If false, ignore effect of admin role assignment
1019  * @param string $errorstring The error string to to user. Defaults to 'nopermissions'.
1020  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
1021  * @return void terminates with an error if the user does not have the given capability.
1022  */
1023 function require_capability($capability, $context, $userid = null, $doanything = true,
1024                             $errormessage = 'nopermissions', $stringfile = '') {
1025     if (!has_capability($capability, $context, $userid, $doanything)) {
1026         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
1027     }
1030 /**
1031  * Get an array of courses where cap requested is available
1032  * and user is enrolled, this can be relatively slow.
1033  *
1034  * @param string $capability - name of the capability
1035  * @param array  $accessdata_ignored
1036  * @param bool   $doanything_ignored
1037  * @param string $sort - sorting fields - prefix each fieldname with "c."
1038  * @param array  $fields - additional fields you are interested in...
1039  * @param int    $limit_ignored
1040  * @return array $courses - ordered array of course objects - see notes above
1041  */
1042 function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort = 'c.sortorder ASC', $fields = null, $limit_ignored = 0) {
1044     //TODO: this should be most probably deprecated
1046     $courses = enrol_get_users_courses($userid, true, $fields, $sort);
1047     foreach ($courses as $id=>$course) {
1048         $context = get_context_instance(CONTEXT_COURSE, $id);
1049         if (!has_capability($cap, $context, $userid)) {
1050             unset($courses[$id]);
1051         }
1052     }
1054     return $courses;
1058 /**
1059  * Return a nested array showing role assignments
1060  * all relevant role capabilities for the user at
1061  * site/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/][]=roleid
1067  * [rdef] => [/path/:roleid][capability]=permission
1068  * [loaded] => array('/path', '/path')
1069  *
1070  * @param int $userid - the id of the user
1071  * @return array
1072  */
1073 function get_user_access_sitewide($userid) {
1074     global $CFG, $DB;
1076     /* Get in 3 cheap DB queries...
1077      * - role assignments
1078      * - relevant role caps
1079      *   - above and within this user's RAs
1080      *   - below this user's RAs - limited to course level
1081      */
1083     $accessdata = array(); // named list
1084     $accessdata['ra']     = array();
1085     $accessdata['rdef']   = array();
1086     $accessdata['loaded'] = array();
1088     //
1089     // Role assignments
1090     //
1091     $sql = "SELECT ctx.path, ra.roleid
1092               FROM {role_assignments} ra
1093               JOIN {context} ctx ON ctx.id=ra.contextid
1094              WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE;
1095     $params = array($userid);
1096     $rs = $DB->get_recordset_sql($sql, $params);
1098     //
1099     // raparents collects paths & roles we need to walk up
1100     // the parenthood to build the rdef
1101     //
1102     $raparents = array();
1103     if ($rs) {
1104         foreach ($rs as $ra) {
1105             // RAs leafs are arrays to support multi
1106             // role assignments...
1107             if (!isset($accessdata['ra'][$ra->path])) {
1108                 $accessdata['ra'][$ra->path] = array();
1109             }
1110             $accessdata['ra'][$ra->path][$ra->roleid] = $ra->roleid;
1112             // Concatenate as string the whole path (all related context)
1113             // for this role. This is damn faster than using array_merge()
1114             // Will unique them later
1115             if (isset($raparents[$ra->roleid])) {
1116                 $raparents[$ra->roleid] .= $ra->path;
1117             } else {
1118                 $raparents[$ra->roleid] = $ra->path;
1119             }
1120         }
1121         unset($ra);
1122         $rs->close();
1123     }
1125     // Walk up the tree to grab all the roledefs
1126     // of interest to our user...
1127     //
1128     // NOTE: we use a series of IN clauses here - which
1129     // might explode on huge sites with very convoluted nesting of
1130     // categories... - extremely unlikely that the number of categories
1131     // and roletypes is so large that we hit the limits of IN()
1132     $clauses = '';
1133     $cparams = array();
1134     foreach ($raparents as $roleid=>$strcontexts) {
1135         $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/'))));
1136         if ($contexts ==! '') {
1137             if ($clauses) {
1138                 $clauses .= ' OR ';
1139             }
1140             $clauses .= "(roleid=? AND contextid IN ($contexts))";
1141             $cparams[] = $roleid;
1142         }
1143     }
1145     if ($clauses !== '') {
1146         $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1147                   FROM {role_capabilities} rc
1148                   JOIN {context} ctx ON rc.contextid=ctx.id
1149                  WHERE $clauses";
1151         unset($clauses);
1152         $rs = $DB->get_recordset_sql($sql, $cparams);
1154         if ($rs) {
1155             foreach ($rs as $rd) {
1156                 $k = "{$rd->path}:{$rd->roleid}";
1157                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1158             }
1159             unset($rd);
1160             $rs->close();
1161         }
1162     }
1164     //
1165     // Overrides for the role assignments IN SUBCONTEXTS
1166     // (though we still do _not_ go below the course level.
1167     //
1168     // NOTE that the JOIN w sctx is with 3-way triangulation to
1169     // catch overrides to the applicable role in any subcontext, based
1170     // on the path field of the parent.
1171     //
1172     $sql = "SELECT sctx.path, ra.roleid,
1173                    ctx.path AS parentpath,
1174                    rco.capability, rco.permission
1175               FROM {role_assignments} ra
1176               JOIN {context} ctx
1177                    ON ra.contextid=ctx.id
1178               JOIN {context} sctx
1179                    ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " )
1180               JOIN {role_capabilities} rco
1181                    ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1182              WHERE ra.userid = ?
1183                    AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
1184                    AND sctx.contextlevel <= ".CONTEXT_COURSE."
1185           ORDER BY sctx.depth, sctx.path, ra.roleid";
1186     $params = array($userid);
1187     $rs = $DB->get_recordset_sql($sql, $params);
1188     if ($rs) {
1189         foreach ($rs as $rd) {
1190             $k = "{$rd->path}:{$rd->roleid}";
1191             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1192         }
1193         unset($rd);
1194         $rs->close();
1195     }
1196     return $accessdata;
1199 /**
1200  * Add to the access ctrl array the data needed by a user for a given context
1201  *
1202  * @param integer $userid the id of the user
1203  * @param object $context needs path!
1204  * @param array $accessdata accessdata array
1205  * @return void
1206  */
1207 function load_subcontext($userid, $context, &$accessdata) {
1208     global $CFG, $DB;
1210     /* Get the additional RAs and relevant rolecaps
1211      * - role assignments - with role_caps
1212      * - relevant role caps
1213      *   - above this user's RAs
1214      *   - below this user's RAs - limited to course level
1215      */
1217     $base = "/" . SYSCONTEXTID;
1219     //
1220     // Replace $context with the target context we will
1221     // load. Normally, this will be a course context, but
1222     // may be a different top-level context.
1223     //
1224     // We have 3 cases
1225     //
1226     // - Course
1227     // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1228     // - BLOCK/MODULE/GROUP hanging from a course
1229     //
1230     // For course contexts, we _already_ have the RAs
1231     // but the cost of re-fetching is minimal so we don't care.
1232     //
1233     if ($context->contextlevel !== CONTEXT_COURSE
1234         && $context->path !== "$base/{$context->id}") {
1235         // Case BLOCK/MODULE/GROUP hanging from a course
1236         // Assumption: the course _must_ be our parent
1237         // If we ever see stuff nested further this needs to
1238         // change to do 1 query over the exploded path to
1239         // find out which one is the course
1240         $courses = explode('/',get_course_from_path($context->path));
1241         $targetid = array_pop($courses);
1242         $context = get_context_instance_by_id($targetid);
1244     }
1246     //
1247     // Role assignments in the context and below
1248     //
1249     $sql = "SELECT ctx.path, ra.roleid
1250               FROM {role_assignments} ra
1251               JOIN {context} ctx
1252                    ON ra.contextid=ctx.id
1253              WHERE ra.userid = ?
1254                    AND (ctx.path = ? OR ctx.path LIKE ?)
1255           ORDER BY ctx.depth, ctx.path, ra.roleid";
1256     $params = array($userid, $context->path, $context->path."/%");
1257     $rs = $DB->get_recordset_sql($sql, $params);
1259     //
1260     // Read in the RAs, preventing duplicates
1261     //
1262     if ($rs) {
1263         $localroles = array();
1264         $lastseen  = '';
1265         foreach ($rs as $ra) {
1266             if (!isset($accessdata['ra'][$ra->path])) {
1267                 $accessdata['ra'][$ra->path] = array();
1268             }
1269             // only add if is not a repeat caused
1270             // by capability join...
1271             // (this check is cheaper than in_array())
1272             if ($lastseen !== $ra->path.':'.$ra->roleid) {
1273                 $lastseen = $ra->path.':'.$ra->roleid;
1274                 $accessdata['ra'][$ra->path][$ra->roleid] = $ra->roleid;
1275                 array_push($localroles,           $ra->roleid);
1276             }
1277         }
1278         $rs->close();
1279     }
1281     //
1282     // Walk up and down the tree to grab all the roledefs
1283     // of interest to our user...
1284     //
1285     // NOTES
1286     // - we use IN() but the number of roles is very limited.
1287     //
1288     $courseroles    = aggregate_roles_from_accessdata($context, $accessdata);
1290     // Do we have any interesting "local" roles?
1291     $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1292     $wherelocalroles='';
1293     if (count($localroles)) {
1294         // Role defs for local roles in 'higher' contexts...
1295         $contexts = substr($context->path, 1); // kill leading slash
1296         $contexts = str_replace('/', ',', $contexts);
1297         $localroleids = implode(',',$localroles);
1298         $wherelocalroles="OR (rc.roleid IN ({$localroleids})
1299                               AND ctx.id IN ($contexts))" ;
1300     }
1302     // We will want overrides for all of them
1303     $whereroles = '';
1304     if ($roleids  = implode(',',array_merge($courseroles,$localroles))) {
1305         $whereroles = "rc.roleid IN ($roleids) AND";
1306     }
1307     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1308               FROM {role_capabilities} rc
1309               JOIN {context} ctx
1310                    ON rc.contextid=ctx.id
1311              WHERE ($whereroles
1312                     (ctx.id=? OR ctx.path LIKE ?))
1313                    $wherelocalroles
1314           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1315     $params = array($context->id, $context->path."/%");
1317     $newrdefs = array();
1318     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1319         foreach ($rs as $rd) {
1320             $k = "{$rd->path}:{$rd->roleid}";
1321             if (!array_key_exists($k, $newrdefs)) {
1322                 $newrdefs[$k] = array();
1323             }
1324             $newrdefs[$k][$rd->capability] = $rd->permission;
1325         }
1326         $rs->close();
1327     } else {
1328         debugging('Bad SQL encountered!');
1329     }
1331     compact_rdefs($newrdefs);
1332     foreach ($newrdefs as $key=>$value) {
1333         $accessdata['rdef'][$key] =& $newrdefs[$key];
1334     }
1336     // error_log("loaded {$context->path}");
1337     $accessdata['loaded'][] = $context->path;
1340 /**
1341  * Add to the access ctrl array the data needed by a role for a given context.
1342  *
1343  * The data is added in the rdef key.
1344  *
1345  * This role-centric function is useful for role_switching
1346  * and to get an overview of what a role gets under a
1347  * given context and below...
1348  *
1349  * @param integer $roleid the id of the user
1350  * @param object $context needs path!
1351  * @param array $accessdata accessdata array null by default
1352  * @return array
1353  */
1354 function get_role_access_bycontext($roleid, $context, $accessdata = null) {
1355     global $CFG, $DB;
1357     /* Get the relevant rolecaps into rdef
1358      * - relevant role caps
1359      *   - at ctx and above
1360      *   - below this ctx
1361      */
1363     if (is_null($accessdata)) {
1364         $accessdata           = array(); // named list
1365         $accessdata['ra']     = array();
1366         $accessdata['rdef']   = array();
1367         $accessdata['loaded'] = array();
1368     }
1370     $contexts = substr($context->path, 1); // kill leading slash
1371     $contexts = str_replace('/', ',', $contexts);
1373     //
1374     // Walk up and down the tree to grab all the roledefs
1375     // of interest to our role...
1376     //
1377     // NOTE: we use an IN clauses here - which
1378     // might explode on huge sites with very convoluted nesting of
1379     // categories... - extremely unlikely that the number of nested
1380     // categories is so large that we hit the limits of IN()
1381     //
1382     $sql = "SELECT ctx.path, rc.capability, rc.permission
1383               FROM {role_capabilities} rc
1384               JOIN {context} ctx
1385                    ON rc.contextid=ctx.id
1386              WHERE rc.roleid=? AND
1387                    ( ctx.id IN ($contexts) OR
1388                     ctx.path LIKE ? )
1389           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1390     $params = array($roleid, $context->path."/%");
1392     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1393         foreach ($rs as $rd) {
1394             $k = "{$rd->path}:{$roleid}";
1395             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1396         }
1397         $rs->close();
1398     }
1400     return $accessdata;
1403 /**
1404  * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global
1405  *
1406  * Used by has_capability() - but feel free
1407  * to call it if you are about to run a BIG
1408  * cron run across a bazillion users.
1409  *
1410  * @param int $userid
1411  * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid]
1412  */
1413 function load_user_accessdata($userid) {
1414     global $CFG, $ACCESSLIB_PRIVATE;
1416     $base = '/'.SYSCONTEXTID;
1418     $accessdata = get_user_access_sitewide($userid);
1419     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1420     //
1421     // provide "default role" & set 'dr'
1422     //
1423     if (!empty($CFG->defaultuserroleid)) {
1424         $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1425         if (!isset($accessdata['ra'][$base])) {
1426             $accessdata['ra'][$base] = array();
1427         }
1428         $accessdata['ra'][$base][$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
1429         $accessdata['dr'] = $CFG->defaultuserroleid;
1430     }
1432     //
1433     // provide "default frontpage role"
1434     //
1435     if (!empty($CFG->defaultfrontpageroleid)) {
1436         $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1437         $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1438         if (!isset($accessdata['ra'][$base])) {
1439             $accessdata['ra'][$base] = array();
1440         }
1441         $accessdata['ra'][$base][$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
1442     }
1443     // for dirty timestamps in cron
1444     $accessdata['time'] = time();
1446     $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1447     compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']);
1449     return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1452 /**
1453  * Use shared copy of role definitions stored in ACCESSLIB_PRIVATE->roledefinitions;
1454  *
1455  * @param array $rdefs array of role definitions in contexts
1456  */
1457 function compact_rdefs(&$rdefs) {
1458     global $ACCESSLIB_PRIVATE;
1460     /*
1461      * This is a basic sharing only, we could also
1462      * use md5 sums of values. The main purpose is to
1463      * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array.
1464      */
1466     foreach ($rdefs as $key => $value) {
1467         if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) {
1468             $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key];
1469         }
1470         $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key];
1471     }
1474 /**
1475  * A convenience function to completely load all the capabilities
1476  * for the current user.   This is what gets called from complete_user_login()
1477  * for example. Call it only _after_ you've setup $USER and called
1478  * check_enrolment_plugins();
1479  * @see check_enrolment_plugins()
1480  *
1481  * @return void
1482  */
1483 function load_all_capabilities() {
1484     global $CFG, $ACCESSLIB_PRIVATE;
1486     //NOTE: we can not use $USER here because it may no be linked to $_SESSION['USER'] yet!
1488     // roles not installed yet - we are in the middle of installation
1489     if (during_initial_install()) {
1490         return;
1491     }
1493     $base = '/'.SYSCONTEXTID;
1495     if (isguestuser($_SESSION['USER'])) {
1496         $guest = get_guest_role();
1498         // Load the rdefs
1499         $_SESSION['USER']->access = get_role_access($guest->id);
1500         // Put the ghost enrolment in place...
1501         $_SESSION['USER']->access['ra'][$base] = array($guest->id => $guest->id);
1504     } else if (!empty($_SESSION['USER']->id)) { // can not use isloggedin() yet
1506         $accessdata = get_user_access_sitewide($_SESSION['USER']->id);
1508         //
1509         // provide "default role" & set 'dr'
1510         //
1511         if (!empty($CFG->defaultuserroleid)) {
1512             $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1513             if (!isset($accessdata['ra'][$base])) {
1514                 $accessdata['ra'][$base] = array();
1515             }
1516             $accessdata['ra'][$base][$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
1517             $accessdata['dr'] = $CFG->defaultuserroleid;
1518         }
1520         $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1522         //
1523         // provide "default frontpage role"
1524         //
1525         if (!empty($CFG->defaultfrontpageroleid)) {
1526             $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1527             $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1528             if (!isset($accessdata['ra'][$base])) {
1529                 $accessdata['ra'][$base] = array();
1530             }
1531             $accessdata['ra'][$base][$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
1532         }
1533         $_SESSION['USER']->access = $accessdata;
1535     } else if (!empty($CFG->notloggedinroleid)) {
1536         $_SESSION['USER']->access = get_role_access($CFG->notloggedinroleid);
1537         $_SESSION['USER']->access['ra'][$base] = array($CFG->notloggedinroleid => $CFG->notloggedinroleid);
1538     }
1540     // Timestamp to read dirty context timestamps later
1541     $_SESSION['USER']->access['time'] = time();
1542     $ACCESSLIB_PRIVATE->dirtycontexts = array();
1544     // Clear to force a refresh
1545     unset($_SESSION['USER']->mycourses);
1548 /**
1549  * A convenience function to completely reload all the capabilities
1550  * for the current user when roles have been updated in a relevant
1551  * context -- but PRESERVING switchroles and loginas.
1552  *
1553  * That is - completely transparent to the user.
1554  *
1555  * Note: rewrites $USER->access completely.
1556  *
1557  * @return void
1558  */
1559 function reload_all_capabilities() {
1560     global $USER, $DB;
1562     // error_log("reloading");
1563     // copy switchroles
1564     $sw = array();
1565     if (isset($USER->access['rsw'])) {
1566         $sw = $USER->access['rsw'];
1567         // error_log(print_r($sw,1));
1568     }
1570     unset($USER->access);
1571     unset($USER->mycourses);
1573     load_all_capabilities();
1575     foreach ($sw as $path => $roleid) {
1576         $context = $DB->get_record('context', array('path'=>$path));
1577         role_switch($roleid, $context);
1578     }
1582 /**
1583  * Adds a temp role to an accessdata array.
1584  *
1585  * Useful for the "temporary guest" access
1586  * we grant to logged-in users.
1587  *
1588  * Note - assumes a course context!
1589  *
1590  * @param object $content
1591  * @param int $roleid
1592  * @param array $accessdata
1593  * @return array Returns access data
1594  */
1595 function load_temp_role($context, $roleid, array $accessdata) {
1596     global $CFG, $DB;
1598     //
1599     // Load rdefs for the role in -
1600     // - this context
1601     // - all the parents
1602     // - and below - IOWs overrides...
1603     //
1605     // turn the path into a list of context ids
1606     $contexts = substr($context->path, 1); // kill leading slash
1607     $contexts = str_replace('/', ',', $contexts);
1609     $sql = "SELECT ctx.path, rc.capability, rc.permission
1610               FROM {context} ctx
1611               JOIN {role_capabilities} rc
1612                    ON rc.contextid=ctx.id
1613              WHERE (ctx.id IN ($contexts)
1614                     OR ctx.path LIKE ?)
1615                    AND rc.roleid = ?
1616           ORDER BY ctx.depth, ctx.path";
1617     $params = array($context->path."/%", $roleid);
1618     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1619         foreach ($rs as $rd) {
1620             $k = "{$rd->path}:{$roleid}";
1621             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1622         }
1623         $rs->close();
1624     }
1626     //
1627     // Say we loaded everything for the course context
1628     // - which we just did - if the user gets a proper
1629     // RA in this session, this data will need to be reloaded,
1630     // but that is handled by the complete accessdata reload
1631     //
1632     array_push($accessdata['loaded'], $context->path);
1634     //
1635     // Add the ghost RA
1636     //
1637     if (!isset($accessdata['ra'][$context->path])) {
1638         $accessdata['ra'][$context->path] = array();
1639     }
1640     $accessdata['ra'][$context->path][$roleid] = $roleid;
1642     return $accessdata;
1645 /**
1646  * Removes any extra guest roles from accessdata
1647  * @param object $context
1648  * @param array $accessdata
1649  * @return array access data
1650  */
1651 function remove_temp_roles($context, array $accessdata) {
1652     global $DB, $USER;
1653     $sql = "SELECT DISTINCT ra.roleid AS id
1654               FROM {role_assignments} ra
1655              WHERE ra.contextid = :contextid AND ra.userid = :userid";
1656     $ras = $DB->get_records_sql($sql, array('contextid'=>$context->id, 'userid'=>$USER->id));
1658     if ($ras) {
1659         $accessdata['ra'][$context->path] = array_combine(array_keys($ras), array_keys($ras));
1660     } else {
1661         $accessdata['ra'][$context->path] = array();
1662     }
1664     return $accessdata;
1667 /**
1668  * Returns array of all role archetypes.
1669  *
1670  * @return array
1671  */
1672 function get_role_archetypes() {
1673     return array(
1674         'manager'        => 'manager',
1675         'coursecreator'  => 'coursecreator',
1676         'editingteacher' => 'editingteacher',
1677         'teacher'        => 'teacher',
1678         'student'        => 'student',
1679         'guest'          => 'guest',
1680         'user'           => 'user',
1681         'frontpage'      => 'frontpage'
1682     );
1685 /**
1686  * Assign the defaults found in this capability definition to roles that have
1687  * the corresponding legacy capabilities assigned to them.
1688  *
1689  * @param string $capability
1690  * @param array $legacyperms an array in the format (example):
1691  *                      'guest' => CAP_PREVENT,
1692  *                      'student' => CAP_ALLOW,
1693  *                      'teacher' => CAP_ALLOW,
1694  *                      'editingteacher' => CAP_ALLOW,
1695  *                      'coursecreator' => CAP_ALLOW,
1696  *                      'manager' => CAP_ALLOW
1697  * @return boolean success or failure.
1698  */
1699 function assign_legacy_capabilities($capability, $legacyperms) {
1701     $archetypes = get_role_archetypes();
1703     foreach ($legacyperms as $type => $perm) {
1705         $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1706         if ($type === 'admin') {
1707             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1708             $type = 'manager';
1709         }
1711         if (!array_key_exists($type, $archetypes)) {
1712             print_error('invalidlegacy', '', '', $type);
1713         }
1715         if ($roles = get_archetype_roles($type)) {
1716             foreach ($roles as $role) {
1717                 // Assign a site level capability.
1718                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1719                     return false;
1720                 }
1721             }
1722         }
1723     }
1724     return true;
1727 /**
1728  * @param object $capability a capability - a row from the capabilities table.
1729  * @return boolean whether this capability is safe - that is, whether people with the
1730  *      safeoverrides capability should be allowed to change it.
1731  */
1732 function is_safe_capability($capability) {
1733     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1736 /**********************************
1737  * Context Manipulation functions *
1738  **********************************/
1740 /**
1741  * Context creation - internal implementation.
1742  *
1743  * Create a new context record for use by all roles-related stuff
1744  * assumes that the caller has done the homework.
1745  *
1746  * DO NOT CALL THIS DIRECTLY, instead use {@link get_context_instance}!
1747  *
1748  * @param int $contextlevel
1749  * @param int $instanceid
1750  * @param int $strictness
1751  * @return object newly created context
1752  */
1753 function create_context($contextlevel, $instanceid, $strictness = IGNORE_MISSING) {
1754     global $CFG, $DB;
1756     if ($contextlevel == CONTEXT_SYSTEM) {
1757         return get_system_context();
1758     }
1760     $context = new stdClass();
1761     $context->contextlevel = $contextlevel;
1762     $context->instanceid = $instanceid;
1764     // Define $context->path based on the parent
1765     // context. In other words... Who is your daddy?
1766     $basepath  = '/' . SYSCONTEXTID;
1767     $basedepth = 1;
1769     $result = true;
1770     $error_message = null;
1772     switch ($contextlevel) {
1773         case CONTEXT_COURSECAT:
1774             $sql = "SELECT ctx.path, ctx.depth
1775                       FROM {context}           ctx
1776                       JOIN {course_categories} cc
1777                            ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1778                      WHERE cc.id=?";
1779             $params = array($instanceid);
1780             if ($p = $DB->get_record_sql($sql, $params)) {
1781                 $basepath  = $p->path;
1782                 $basedepth = $p->depth;
1783             } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), '*', $strictness)) {
1784                 if (empty($category->parent)) {
1785                     // ok - this is a top category
1786                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
1787                     $basepath  = $parent->path;
1788                     $basedepth = $parent->depth;
1789                 } else {
1790                     // wrong parent category - no big deal, this can be fixed later
1791                     $basepath  = null;
1792                     $basedepth = 0;
1793                 }
1794             } else {
1795                 // incorrect category id
1796                 $error_message = "incorrect course category id ($instanceid)";
1797                 $result = false;
1798             }
1799             break;
1801         case CONTEXT_COURSE:
1802             $sql = "SELECT ctx.path, ctx.depth
1803                       FROM {context} ctx
1804                       JOIN {course}  c
1805                            ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1806                      WHERE c.id=? AND c.id !=" . SITEID;
1807             $params = array($instanceid);
1808             if ($p = $DB->get_record_sql($sql, $params)) {
1809                 $basepath  = $p->path;
1810                 $basedepth = $p->depth;
1811             } else if ($course = $DB->get_record('course', array('id'=>$instanceid), '*', $strictness)) {
1812                 if ($course->id == SITEID) {
1813                     //ok - no parent category
1814                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
1815                     $basepath  = $parent->path;
1816                     $basedepth = $parent->depth;
1817                 } else {
1818                     // wrong parent category of course - no big deal, this can be fixed later
1819                     $basepath  = null;
1820                     $basedepth = 0;
1821                 }
1822             } else if ($instanceid == SITEID) {
1823                 // no errors for missing site course during installation
1824                 return false;
1825             } else {
1826                 // incorrect course id
1827                 $error_message = "incorrect course id ($instanceid)";
1828                 $result = false;
1829             }
1830             break;
1832         case CONTEXT_MODULE:
1833             $sql = "SELECT ctx.path, ctx.depth
1834                       FROM {context}        ctx
1835                       JOIN {course_modules} cm
1836                            ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1837                      WHERE cm.id=?";
1838             $params = array($instanceid);
1839             if ($p = $DB->get_record_sql($sql, $params)) {
1840                 $basepath  = $p->path;
1841                 $basedepth = $p->depth;
1842             } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), '*', $strictness)) {
1843                 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course, $strictness)) {
1844                     $basepath  = $parent->path;
1845                     $basedepth = $parent->depth;
1846                 } else {
1847                     // course does not exist - modules can not exist without a course
1848                     $error_message = "course does not exist ($cm->course) - modules can not exist without a course";
1849                     $result = false;
1850                 }
1851             } else {
1852                 // cm does not exist
1853                 $error_message = "cm with id $instanceid does not exist";
1854                 $result = false;
1855             }
1856             break;
1858         case CONTEXT_BLOCK:
1859             $sql = "SELECT ctx.path, ctx.depth
1860                       FROM {context} ctx
1861                       JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id)
1862                      WHERE bi.id = ?";
1863             $params = array($instanceid, CONTEXT_COURSE);
1864             if ($p = $DB->get_record_sql($sql, $params, '*', $strictness)) {
1865                 $basepath  = $p->path;
1866                 $basedepth = $p->depth;
1867             } else {
1868                 // block does not exist
1869                 $error_message = 'block or parent context does not exist';
1870                 $result = false;
1871             }
1872             break;
1873         case CONTEXT_USER:
1874             // default to basepath
1875             break;
1876     }
1878     // if grandparents unknown, maybe rebuild_context_path() will solve it later
1879     if ($basedepth != 0) {
1880         $context->depth = $basedepth+1;
1881     }
1883     if (!$result) {
1884         debugging('Error: could not insert new context level "'.
1885                   s($contextlevel).'", instance "'.
1886                   s($instanceid).'". ' . $error_message);
1888         return false;
1889     }
1891     $id = $DB->insert_record('context', $context);
1892     // can't set the full path till we know the id!
1893     if ($basedepth != 0 and !empty($basepath)) {
1894         $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
1895     }
1896     return get_context_instance_by_id($id);
1899 /**
1900  * Returns system context or null if can not be created yet.
1901  *
1902  * @param bool $cache use caching
1903  * @return mixed system context or null
1904  */
1905 function get_system_context($cache = true) {
1906     global $DB, $ACCESSLIB_PRIVATE;
1907     if ($cache and defined('SYSCONTEXTID')) {
1908         if (is_null($ACCESSLIB_PRIVATE->systemcontext)) {
1909             $ACCESSLIB_PRIVATE->systemcontext = new stdClass();
1910             $ACCESSLIB_PRIVATE->systemcontext->id           = SYSCONTEXTID;
1911             $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM;
1912             $ACCESSLIB_PRIVATE->systemcontext->instanceid   = 0;
1913             $ACCESSLIB_PRIVATE->systemcontext->path         = '/'.SYSCONTEXTID;
1914             $ACCESSLIB_PRIVATE->systemcontext->depth        = 1;
1915         }
1916         return $ACCESSLIB_PRIVATE->systemcontext;
1917     }
1918     try {
1919         $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM));
1920     } catch (dml_exception $e) {
1921         //table does not exist yet, sorry
1922         return null;
1923     }
1925     if (!$context) {
1926         $context = new stdClass();
1927         $context->contextlevel = CONTEXT_SYSTEM;
1928         $context->instanceid   = 0;
1929         $context->depth        = 1;
1930         $context->path         = null; //not known before insert
1932         try {
1933             $context->id = $DB->insert_record('context', $context);
1934         } catch (dml_exception $e) {
1935             // can not create context yet, sorry
1936             return null;
1937         }
1938     }
1940     if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
1941         $context->instanceid   = 0;
1942         $context->path         = '/'.$context->id;
1943         $context->depth        = 1;
1944         $DB->update_record('context', $context);
1945     }
1947     if (!defined('SYSCONTEXTID')) {
1948         define('SYSCONTEXTID', $context->id);
1949     }
1951     $ACCESSLIB_PRIVATE->systemcontext = $context;
1952     return $ACCESSLIB_PRIVATE->systemcontext;
1955 /**
1956  * Remove a context record and any dependent entries,
1957  * removes context from static context cache too
1958  *
1959  * @param int $level
1960  * @param int $instanceid
1961  * @param bool $deleterecord false means keep record for now
1962  * @return bool returns true or throws an exception
1963  */
1964 function delete_context($contextlevel, $instanceid, $deleterecord = true) {
1965     global $DB, $ACCESSLIB_PRIVATE, $CFG;
1967     // do not use get_context_instance(), because the related object might not exist,
1968     // or the context does not exist yet and it would be created now
1969     if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
1970         // delete these first because they might fetch the context and try to recreate it!
1971         blocks_delete_all_for_context($context->id);
1972         filter_delete_all_for_context($context->id);
1974         require_once($CFG->dirroot . '/comment/lib.php');
1975         comment::delete_comments(array('contextid'=>$context->id));
1977         require_once($CFG->dirroot.'/rating/lib.php');
1978         $delopt = new stdclass();
1979         $delopt->contextid = $context->id;
1980         $rm = new rating_manager();
1981         $rm->delete_ratings($delopt);
1983         // delete all files attached to this context
1984         $fs = get_file_storage();
1985         $fs->delete_area_files($context->id);
1987         // now delete stuff from role related tables, role_unassign_all
1988         // and unenrol should be called earlier to do proper cleanup
1989         $DB->delete_records('role_assignments', array('contextid'=>$context->id));
1990         $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
1991         $DB->delete_records('role_names', array('contextid'=>$context->id));
1993         // and finally it is time to delete the context record if requested
1994         if ($deleterecord) {
1995             $DB->delete_records('context', array('id'=>$context->id));
1996             // purge static context cache if entry present
1997             $ACCESSLIB_PRIVATE->contexcache->remove($context);
1998         }
2000         // do not mark dirty contexts if parents unknown
2001         if (!is_null($context->path) and $context->depth > 0) {
2002             mark_context_dirty($context->path);
2003         }
2004     }
2006     return true;
2009 /**
2010  * Precreates all contexts including all parents
2011  *
2012  * @param int $contextlevel empty means all
2013  * @param bool $buildpaths update paths and depths
2014  * @return void
2015  */
2016 function create_contexts($contextlevel = null, $buildpaths = true) {
2017     global $DB;
2019     //make sure system context exists
2020     $syscontext = get_system_context(false);
2022     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2023                              or $contextlevel == CONTEXT_COURSE
2024                              or $contextlevel == CONTEXT_MODULE
2025                              or $contextlevel == CONTEXT_BLOCK) {
2026         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2027                 SELECT ".CONTEXT_COURSECAT.", cc.id
2028                   FROM {course}_categories cc
2029                  WHERE NOT EXISTS (SELECT 'x'
2030                                      FROM {context} cx
2031                                     WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
2032         $DB->execute($sql);
2034     }
2036     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2037                              or $contextlevel == CONTEXT_MODULE
2038                              or $contextlevel == CONTEXT_BLOCK) {
2039         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2040                 SELECT ".CONTEXT_COURSE.", c.id
2041                   FROM {course} c
2042                  WHERE NOT EXISTS (SELECT 'x'
2043                                      FROM {context} cx
2044                                     WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
2045         $DB->execute($sql);
2047     }
2049     if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
2050                              or $contextlevel == CONTEXT_BLOCK) {
2051         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2052                 SELECT ".CONTEXT_MODULE.", cm.id
2053                   FROM {course}_modules cm
2054                  WHERE NOT EXISTS (SELECT 'x'
2055                                      FROM {context} cx
2056                                     WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
2057         $DB->execute($sql);
2058     }
2060     if (empty($contextlevel) or $contextlevel == CONTEXT_USER
2061                              or $contextlevel == CONTEXT_BLOCK) {
2062         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2063                 SELECT ".CONTEXT_USER.", u.id
2064                   FROM {user} u
2065                  WHERE u.deleted=0
2066                    AND NOT EXISTS (SELECT 'x'
2067                                      FROM {context} cx
2068                                     WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
2069         $DB->execute($sql);
2071     }
2073     if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
2074         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2075                 SELECT ".CONTEXT_BLOCK.", bi.id
2076                   FROM {block_instances} bi
2077                  WHERE NOT EXISTS (SELECT 'x'
2078                                      FROM {context} cx
2079                                     WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2080         $DB->execute($sql);
2081     }
2083     if ($buildpaths) {
2084         build_context_path(false);
2085     }
2088 /**
2089  * Remove stale context records
2090  *
2091  * @return bool
2092  */
2093 function cleanup_contexts() {
2094     global $DB;
2096     $sql = "  SELECT c.contextlevel,
2097                      c.instanceid AS instanceid
2098                 FROM {context} c
2099                 LEFT OUTER JOIN {course}_categories t
2100                      ON c.instanceid = t.id
2101                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
2102             UNION
2103               SELECT c.contextlevel,
2104                      c.instanceid
2105                 FROM {context} c
2106                 LEFT OUTER JOIN {course} t
2107                      ON c.instanceid = t.id
2108                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
2109             UNION
2110               SELECT c.contextlevel,
2111                      c.instanceid
2112                 FROM {context} c
2113                 LEFT OUTER JOIN {course}_modules t
2114                      ON c.instanceid = t.id
2115                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
2116             UNION
2117               SELECT c.contextlevel,
2118                      c.instanceid
2119                 FROM {context} c
2120                 LEFT OUTER JOIN {user} t
2121                      ON c.instanceid = t.id
2122                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
2123             UNION
2124               SELECT c.contextlevel,
2125                      c.instanceid
2126                 FROM {context} c
2127                 LEFT OUTER JOIN {block_instances} t
2128                      ON c.instanceid = t.id
2129                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
2130            ";
2132     // transactions used only for performance reasons here
2133     $transaction = $DB->start_delegated_transaction();
2135     if ($rs = $DB->get_recordset_sql($sql)) {
2136         foreach ($rs as $ctx) {
2137             delete_context($ctx->contextlevel, $ctx->instanceid);
2138         }
2139         $rs->close();
2140     }
2142     $transaction->allow_commit();
2143     return true;
2146 /**
2147  * Preloads all contexts relating to a course: course, modules. Block contexts
2148  * are no longer loaded here. The contexts for all the blocks on the current
2149  * page are now efficiently loaded by {@link block_manager::load_blocks()}.
2150  *
2151  * @param int $courseid Course ID
2152  * @return void
2153  */
2154 function preload_course_contexts($courseid) {
2155     global $DB, $ACCESSLIB_PRIVATE;
2157     // Users can call this multiple times without doing any harm
2158     global $ACCESSLIB_PRIVATE;
2159     if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
2160         return;
2161     }
2163     $params = array($courseid, $courseid, $courseid);
2164     $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2165               FROM {course_modules} cm
2166               JOIN {context} x ON x.instanceid=cm.id
2167              WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2169          UNION ALL
2171             SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2172               FROM {context} x
2173              WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2175     $rs = $DB->get_recordset_sql($sql, $params);
2176     foreach($rs as $context) {
2177         $ACCESSLIB_PRIVATE->contexcache->add($context);
2178     }
2179     $rs->close();
2180     $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
2183 /**
2184  * Get the context instance as an object. This function will create the
2185  * context instance if it does not exist yet.
2186  *
2187  * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2188  *
2189  * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2190  * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2191  *      for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
2192  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2193  *      MUST_EXIST means throw exception if no record or multiple records found
2194  * @return object The context object.
2195  */
2196 function get_context_instance($contextlevel, $instance = 0, $strictness = IGNORE_MISSING) {
2197     global $DB, $ACCESSLIB_PRIVATE;
2198     static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
2200 /// System context has special cache
2201     if ($contextlevel == CONTEXT_SYSTEM) {
2202         return get_system_context();
2203     }
2205 /// check allowed context levels
2206     if (!in_array($contextlevel, $allowed_contexts)) {
2207         // fatal error, code must be fixed - probably typo or switched parameters
2208         print_error('invalidcourselevel');
2209     }
2211     // Various operations rely on context cache
2212     $cache = $ACCESSLIB_PRIVATE->contexcache;
2214     if (!is_array($instance)) {
2215     /// Check the cache
2216         $context = $cache->get($contextlevel, $instance);
2217         if ($context) {
2218             return $context;
2219         }
2221     /// Get it from the database, or create it
2222         if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
2223             $context = create_context($contextlevel, $instance, $strictness);
2224         }
2226     /// Only add to cache if context isn't empty.
2227         if (!empty($context)) {
2228             $cache->add($context);
2229         }
2231         return $context;
2232     }
2235 /// ok, somebody wants to load several contexts to save some db queries ;-)
2236     $instances = $instance;
2237     $result = array();
2239     foreach ($instances as $key=>$instance) {
2240     /// Check the cache first
2241         if ($context = $cache->get($contextlevel, $instance)) {  // Already cached
2242             $result[$instance] = $context;
2243             unset($instances[$key]);
2244             continue;
2245         }
2246     }
2248     if ($instances) {
2249         list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2250         array_unshift($params, $contextlevel);
2251         $sql = "SELECT instanceid, id, contextlevel, path, depth
2252                   FROM {context}
2253                  WHERE contextlevel=? AND instanceid $instanceids";
2255         if (!$contexts = $DB->get_records_sql($sql, $params)) {
2256             $contexts = array();
2257         }
2259         foreach ($instances as $instance) {
2260             if (isset($contexts[$instance])) {
2261                 $context = $contexts[$instance];
2262             } else {
2263                 $context = create_context($contextlevel, $instance);
2264             }
2266             if (!empty($context)) {
2267                 $cache->add($context);
2268             }
2270             $result[$instance] = $context;
2271         }
2272     }
2274     return $result;
2278 /**
2279  * Get a context instance as an object, from a given context id.
2280  *
2281  * @param int $id context id
2282  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2283  *                        MUST_EXIST means throw exception if no record or multiple records found
2284  * @return stdClass|bool the context object or false if not found.
2285  */
2286 function get_context_instance_by_id($id, $strictness = IGNORE_MISSING) {
2287     global $DB, $ACCESSLIB_PRIVATE;
2289     if ($id == SYSCONTEXTID) {
2290         return get_system_context();
2291     }
2293     $cache = $ACCESSLIB_PRIVATE->contexcache;
2294     if ($context = $cache->get_by_id($id)) {
2295         return $context;
2296     }
2298     if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
2299         $cache->add($context);
2300         return $context;
2301     }
2303     return false;
2307 /**
2308  * Get the local override (if any) for a given capability in a role in a context
2309  *
2310  * @param int $roleid
2311  * @param int $contextid
2312  * @param string $capability
2313  */
2314 function get_local_override($roleid, $contextid, $capability) {
2315     global $DB;
2316     return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
2319 /**
2320  * Returns context instance plus related course and cm instances
2321  * @param int $contextid
2322  * @return array of ($context, $course, $cm)
2323  */
2324 function get_context_info_array($contextid) {
2325     global $DB;
2327     $context = get_context_instance_by_id($contextid, MUST_EXIST);
2328     $course  = null;
2329     $cm      = null;
2331     if ($context->contextlevel == CONTEXT_COURSE) {
2332         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2334     } else if ($context->contextlevel == CONTEXT_MODULE) {
2335         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2336         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2338     } else if ($context->contextlevel == CONTEXT_BLOCK) {
2339         $parentcontexts = get_parent_contexts($context, false);
2340         $parent = reset($parentcontexts);
2341         $parent = get_context_instance_by_id($parent);
2343         if ($parent->contextlevel == CONTEXT_COURSE) {
2344             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2345         } else if ($parent->contextlevel == CONTEXT_MODULE) {
2346             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2347             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2348         }
2349     }
2351     return array($context, $course, $cm);
2354 /**
2355  * Returns current course id or null if outside of course based on context parameter.
2356  * @param object $context
2357  * @return int|bool related course id or false
2358  */
2359 function get_courseid_from_context($context) {
2360     if ($context->contextlevel == CONTEXT_COURSE) {
2361         return $context->instanceid;
2362     }
2364     if ($context->contextlevel < CONTEXT_COURSE) {
2365         return false;
2366     }
2368     if ($context->contextlevel == CONTEXT_MODULE) {
2369         $parentcontexts = get_parent_contexts($context, false);
2370         $parent = reset($parentcontexts);
2371         $parent = get_context_instance_by_id($parent);
2372         return $parent->instanceid;
2373     }
2375     if ($context->contextlevel == CONTEXT_BLOCK) {
2376         $parentcontexts = get_parent_contexts($context, false);
2377         $parent = reset($parentcontexts);
2378         return get_courseid_from_context($parent);
2379     }
2381     return false;
2385 //////////////////////////////////////
2386 //    DB TABLE RELATED FUNCTIONS    //
2387 //////////////////////////////////////
2389 /**
2390  * function that creates a role
2391  *
2392  * @param string $name role name
2393  * @param string $shortname role short name
2394  * @param string $description role description
2395  * @param string $archetype
2396  * @return int id or dml_exception
2397  */
2398 function create_role($name, $shortname, $description, $archetype = '') {
2399     global $DB;
2401     if (strpos($archetype, 'moodle/legacy:') !== false) {
2402         throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
2403     }
2405     // verify role archetype actually exists
2406     $archetypes = get_role_archetypes();
2407     if (empty($archetypes[$archetype])) {
2408         $archetype = '';
2409     }
2411     // Get the system context.
2412     $context = get_context_instance(CONTEXT_SYSTEM);
2414     // Insert the role record.
2415     $role = new stdClass();
2416     $role->name        = $name;
2417     $role->shortname   = $shortname;
2418     $role->description = $description;
2419     $role->archetype   = $archetype;
2421     //find free sortorder number
2422     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
2423     if (empty($role->sortorder)) {
2424         $role->sortorder = 1;
2425     }
2426     $id = $DB->insert_record('role', $role);
2428     return $id;
2431 /**
2432  * Function that deletes a role and cleanups up after it
2433  *
2434  * @param int $roleid id of role to delete
2435  * @return bool always true
2436  */
2437 function delete_role($roleid) {
2438     global $CFG, $DB;
2440     // first unssign all users
2441     role_unassign_all(array('roleid'=>$roleid));
2443     // cleanup all references to this role, ignore errors
2444     $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
2445     $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
2446     $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
2447     $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2448     $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2449     $DB->delete_records('role_names',          array('roleid'=>$roleid));
2450     $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
2452     // finally delete the role itself
2453     // get this before the name is gone for logging
2454     $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
2456     $DB->delete_records('role', array('id'=>$roleid));
2458     add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
2460     return true;
2463 /**
2464  * Function to write context specific overrides, or default capabilities.
2465  *
2466  * @param string $capability string name
2467  * @param int $permission CAP_ constants
2468  * @param int $roleid role id
2469  * @param int $contextid context id
2470  * @param bool $overwrite
2471  * @return bool always true or exception
2472  */
2473 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
2474     global $USER, $DB;
2476     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
2477         unassign_capability($capability, $roleid, $contextid);
2478         return true;
2479     }
2481     $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
2483     if ($existing and !$overwrite) {   // We want to keep whatever is there already
2484         return true;
2485     }
2487     $cap = new stdClass();
2488     $cap->contextid    = $contextid;
2489     $cap->roleid       = $roleid;
2490     $cap->capability   = $capability;
2491     $cap->permission   = $permission;
2492     $cap->timemodified = time();
2493     $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
2495     if ($existing) {
2496         $cap->id = $existing->id;
2497         $DB->update_record('role_capabilities', $cap);
2498     } else {
2499         $c = $DB->get_record('context', array('id'=>$contextid));
2500         $DB->insert_record('role_capabilities', $cap);
2501     }
2502     return true;
2505 /**
2506  * Unassign a capability from a role.
2507  *
2508  * @param string $capability the name of the capability
2509  * @param int $roleid the role id
2510  * @param int $contextid null means all contexts
2511  * @return boolean success or failure
2512  */
2513 function unassign_capability($capability, $roleid, $contextid = null) {
2514     global $DB;
2516     if (!empty($contextid)) {
2517         // delete from context rel, if this is the last override in this context
2518         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
2519     } else {
2520         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
2521     }
2522     return true;
2526 /**
2527  * Get the roles that have a given capability assigned to it
2528  *
2529  * This function does not resolve the actual permission of the capability.
2530  * It just checks for permissions and overrides.
2531  * Use get_roles_with_cap_in_context() if resolution is required.
2532  *
2533  * @param string $capability - capability name (string)
2534  * @param string $permission - optional, the permission defined for this capability
2535  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
2536  * @param stdClass $context, null means any
2537  * @return array of role objects
2538  */
2539 function get_roles_with_capability($capability, $permission = null, $context = null) {
2540     global $DB;
2542     if ($context) {
2543         $contexts = get_parent_contexts($context, true);
2544         list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx000');
2545         $contextsql = "AND rc.contextid $insql";
2546     } else {
2547         $params = array();
2548         $contextsql = '';
2549     }
2551     if ($permission) {
2552         $permissionsql = " AND rc.permission = :permission";
2553         $params['permission'] = $permission;
2554     } else {
2555         $permissionsql = '';
2556     }
2558     $sql = "SELECT r.*
2559               FROM {role} r
2560              WHERE r.id IN (SELECT rc.roleid
2561                               FROM {role_capabilities} rc
2562                              WHERE rc.capability = :capname
2563                                    $contextsql
2564                                    $permissionsql)";
2565     $params['capname'] = $capability;
2568     return $DB->get_records_sql($sql, $params);
2572 /**
2573  * This function makes a role-assignment (a role for a user in a particular context)
2574  *
2575  * @param int $roleid the role of the id
2576  * @param int $userid userid
2577  * @param int $contextid id of the context
2578  * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
2579  * @prama int $itemid id of enrolment/auth plugin
2580  * @param string $timemodified defaults to current time
2581  * @return int new/existing id of the assignment
2582  */
2583 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
2584     global $USER, $CFG, $DB;
2586     // first of all detect if somebody is using old style parameters
2587     if ($contextid === 0 or is_numeric($component)) {
2588         throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
2589     }
2591     // now validate all parameters
2592     if (empty($roleid)) {
2593         throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
2594     }
2596     if (empty($userid)) {
2597         throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
2598     }
2600     if ($itemid) {
2601         if (strpos($component, '_') === false) {
2602             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
2603         }
2604     } else {
2605         $itemid = 0;
2606         if ($component !== '' and strpos($component, '_') === false) {
2607             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2608         }
2609     }
2611     if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
2612         throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
2613         return false;
2614     }
2616     $context = get_context_instance_by_id($contextid, MUST_EXIST);
2618     if (!$timemodified) {
2619         $timemodified = time();
2620     }
2622 /// Check for existing entry
2623     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
2625     if ($ras) {
2626         // role already assigned - this should not happen
2627         if (count($ras) > 1) {
2628             //very weird - remove all duplicates!
2629             $ra = array_shift($ras);
2630             foreach ($ras as $r) {
2631                 $DB->delete_records('role_assignments', array('id'=>$r->id));
2632             }
2633         } else {
2634             $ra = reset($ras);
2635         }
2637         // actually there is no need to update, reset anything or trigger any event, so just return
2638         return $ra->id;
2639     }
2641     // Create a new entry
2642     $ra = new stdClass();
2643     $ra->roleid       = $roleid;
2644     $ra->contextid    = $context->id;
2645     $ra->userid       = $userid;
2646     $ra->component    = $component;
2647     $ra->itemid       = $itemid;
2648     $ra->timemodified = $timemodified;
2649     $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
2651     $ra->id = $DB->insert_record('role_assignments', $ra);
2653     // mark context as dirty - again expensive, but needed
2654     mark_context_dirty($context->path);
2656     if (!empty($USER->id) && $USER->id == $userid) {
2657         // If the user is the current user, then do full reload of capabilities too.
2658         load_all_capabilities();
2659     }
2661     events_trigger('role_assigned', $ra);
2663     return $ra->id;
2666 /**
2667  * Removes one role assignment
2668  *
2669  * @param int $roleid
2670  * @param int  $userid
2671  * @param int  $contextid
2672  * @param string $component
2673  * @param int  $itemid
2674  * @return void
2675  */
2676 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
2677     global $USER, $CFG, $DB;
2679     // first make sure the params make sense
2680     if ($roleid == 0 or $userid == 0 or $contextid == 0) {
2681         throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
2682     }
2684     if ($itemid) {
2685         if (strpos($component, '_') === false) {
2686             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
2687         }
2688     } else {
2689         $itemid = 0;
2690         if ($component !== '' and strpos($component, '_') === false) {
2691             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2692         }
2693     }
2695     role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
2698 /**
2699  * Removes multiple role assignments, parameters may contain:
2700  *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
2701  *
2702  * @param array $params role assignment parameters
2703  * @param bool $subcontexts unassign in subcontexts too
2704  * @param bool $includmanual include manual role assignments too
2705  * @return void
2706  */
2707 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
2708     global $USER, $CFG, $DB;
2710     if (!$params) {
2711         throw new coding_exception('Missing parameters in role_unsassign_all() call');
2712     }
2714     $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
2715     foreach ($params as $key=>$value) {
2716         if (!in_array($key, $allowed)) {
2717             throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
2718         }
2719     }
2721     if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
2722         throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
2723     }
2725     if ($includemanual) {
2726         if (!isset($params['component']) or $params['component'] === '') {
2727             throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
2728         }
2729     }
2731     if ($subcontexts) {
2732         if (empty($params['contextid'])) {
2733             throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
2734         }
2735     }
2737     $ras = $DB->get_records('role_assignments', $params);
2738     foreach($ras as $ra) {
2739         $DB->delete_records('role_assignments', array('id'=>$ra->id));
2740         if ($context = get_context_instance_by_id($ra->contextid)) {
2741             // this is a bit expensive but necessary
2742             mark_context_dirty($context->path);
2743             /// If the user is the current user, then do full reload of capabilities too.
2744             if (!empty($USER->id) && $USER->id == $ra->userid) {
2745                 load_all_capabilities();
2746             }
2747         }
2748         events_trigger('role_unassigned', $ra);
2749     }
2750     unset($ras);
2752     // process subcontexts
2753     if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) {
2754         $contexts = get_child_contexts($context);
2755         $mparams = $params;
2756         foreach($contexts as $context) {
2757             $mparams['contextid'] = $context->id;
2758             $ras = $DB->get_records('role_assignments', $mparams);
2759             foreach($ras as $ra) {
2760                 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2761                 // this is a bit expensive but necessary
2762                 mark_context_dirty($context->path);
2763                 /// If the user is the current user, then do full reload of capabilities too.
2764                 if (!empty($USER->id) && $USER->id == $ra->userid) {
2765                     load_all_capabilities();
2766                 }
2767                 events_trigger('role_unassigned', $ra);
2768             }
2769         }
2770     }
2772     // do this once more for all manual role assignments
2773     if ($includemanual) {
2774         $params['component'] = '';
2775         role_unassign_all($params, $subcontexts, false);
2776     }
2780 /**
2781  * Determines if a user is currently logged in
2782  *
2783  * @return bool
2784  */
2785 function isloggedin() {
2786     global $USER;
2788     return (!empty($USER->id));
2791 /**
2792  * Determines if a user is logged in as real guest user with username 'guest'.
2793  *
2794  * @param int|object $user mixed user object or id, $USER if not specified
2795  * @return bool true if user is the real guest user, false if not logged in or other user
2796  */
2797 function isguestuser($user = null) {
2798     global $USER, $DB, $CFG;
2800     // make sure we have the user id cached in config table, because we are going to use it a lot
2801     if (empty($CFG->siteguest)) {
2802         if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
2803             // guest does not exist yet, weird
2804             return false;
2805         }
2806         set_config('siteguest', $guestid);
2807     }
2808     if ($user === null) {
2809         $user = $USER;
2810     }
2812     if ($user === null) {
2813         // happens when setting the $USER
2814         return false;
2816     } else if (is_numeric($user)) {
2817         return ($CFG->siteguest == $user);
2819     } else if (is_object($user)) {
2820         if (empty($user->id)) {
2821             return false; // not logged in means is not be guest
2822         } else {
2823             return ($CFG->siteguest == $user->id);
2824         }
2826     } else {
2827         throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
2828     }
2831 /**
2832  * Does user have a (temporary or real) guest access to course?
2833  *
2834  * @param stdClass $context
2835  * @param stdClass|int $user
2836  * @return bool
2837  */
2838 function is_guest($context, $user = null) {
2839     global $USER;
2841     // first find the course context
2842     $coursecontext = get_course_context($context);
2844     // make sure there is a real user specified
2845     if ($user === null) {
2846         $userid = !empty($USER->id) ? $USER->id : 0;
2847     } else {
2848         $userid = !empty($user->id) ? $user->id : $user;
2849     }
2851     if (isguestuser($userid)) {
2852         // can not inspect or be enrolled
2853         return true;
2854     }
2856     if (has_capability('moodle/course:view', $coursecontext, $user)) {
2857         // viewing users appear out of nowhere, they are neither guests nor participants
2858         return false;
2859     }
2861     // consider only real active enrolments here
2862     if (is_enrolled($coursecontext, $user, '', true)) {
2863         return false;
2864     }
2866     return true;
2870 /**
2871  * Returns true if the user has moodle/course:view capability in the course,
2872  * this is intended for admins, managers (aka small admins), inspectors, etc.
2873  *
2874  * @param stdClass $context
2875  * @param int|object $user, if null $USER is used
2876  * @param string $withcapability extra capability name
2877  * @return bool
2878  */
2879 function is_viewing($context, $user = null, $withcapability = '') {
2880     // first find the course context
2881     $coursecontext = get_course_context($context);
2883     if (isguestuser($user)) {
2884         // can not inspect
2885         return false;
2886     }
2888     if (!has_capability('moodle/course:view', $coursecontext, $user)) {
2889         // admins are allowed to inspect courses
2890         return false;
2891     }
2893     if ($withcapability and !has_capability($withcapability, $context, $user)) {
2894         // site admins always have the capability, but the enrolment above blocks
2895         return false;
2896     }
2898     return true;
2901 /**
2902  * Returns true if user is enrolled (is participating) in course
2903  * this is intended for students and teachers.
2904  *
2905  * @param object $context
2906  * @param int|object $user, if null $USER is used, otherwise user object or id expected
2907  * @param string $withcapability extra capability name
2908  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2909  * @return bool
2910  */
2911 function is_enrolled($context, $user = null, $withcapability = '', $onlyactive = false) {
2912     global $USER, $DB;
2914     // first find the course context
2915     $coursecontext = get_course_context($context);
2917     // make sure there is a real user specified
2918     if ($user === null) {
2919         $userid = !empty($USER->id) ? $USER->id : 0;
2920     } else {
2921         $userid = !empty($user->id) ? $user->id : $user;
2922     }
2924     if (empty($userid)) {
2925         // not-logged-in!
2926         return false;
2927     } else if (isguestuser($userid)) {
2928         // guest account can not be enrolled anywhere
2929         return false;
2930     }
2932     if ($coursecontext->instanceid == SITEID) {
2933         // everybody participates on frontpage
2934     } else {
2935         if ($onlyactive) {
2936             $sql = "SELECT ue.*
2937                       FROM {user_enrolments} ue
2938                       JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2939                       JOIN {user} u ON u.id = ue.userid
2940                      WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
2941             $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2942             // this result should be very small, better not do the complex time checks in sql for now ;-)
2943             $enrolments = $DB->get_records_sql($sql, $params);
2944             $now = time();
2945             // make sure the enrol period is ok
2946             $result = false;
2947             foreach ($enrolments as $e) {
2948                 if ($e->timestart > $now) {
2949                     continue;
2950                 }
2951                 if ($e->timeend and $e->timeend < $now) {
2952                     continue;
2953                 }
2954                 $result = true;
2955                 break;
2956             }
2957             if (!$result) {
2958                 return false;
2959             }
2961         } else {
2962             // any enrolment is good for us here, even outdated, disabled or inactive
2963             $sql = "SELECT 'x'
2964                       FROM {user_enrolments} ue
2965                       JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2966                       JOIN {user} u ON u.id = ue.userid
2967                      WHERE ue.userid = :userid AND u.deleted = 0";
2968             $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2969             if (!$DB->record_exists_sql($sql, $params)) {
2970                 return false;
2971             }
2972         }
2973     }
2975     if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2976         return false;
2977     }
2979     return true;
2982 /**
2983  * Returns array with sql code and parameters returning all ids
2984  * of users enrolled into course.
2985  *
2986  * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2987  *
2988  * @param object $context
2989  * @param string $withcapability
2990  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2991  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2992  * @return array list($sql, $params)
2993  */
2994 function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2995     global $DB, $CFG;
2997     // use unique prefix just in case somebody makes some SQL magic with the result
2998     static $i = 0;
2999     $i++;
3000     $prefix = 'eu'.$i.'_';
3002     // first find the course context
3003     $coursecontext = get_course_context($context);
3005     $isfrontpage = ($coursecontext->instanceid == SITEID);
3007     $joins  = array();
3008     $wheres = array();
3009     $params = array();
3011     list($contextids, $contextpaths) = get_context_info_list($context);
3013     // get all relevant capability info for all roles
3014     if ($withcapability) {
3015         list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx00');
3016         $cparams['cap'] = $withcapability;
3018         $defs = array();
3019         $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
3020                   FROM {role_capabilities} rc
3021                   JOIN {context} ctx on rc.contextid = ctx.id
3022                  WHERE rc.contextid $incontexts AND rc.capability = :cap";
3023         $rcs = $DB->get_records_sql($sql, $cparams);
3024         foreach ($rcs as $rc) {
3025             $defs[$rc->path][$rc->roleid] = $rc->permission;
3026         }
3028         $access = array();
3029         if (!empty($defs)) {
3030             foreach ($contextpaths as $path) {
3031                 if (empty($defs[$path])) {
3032                     continue;
3033                 }
3034                 foreach($defs[$path] as $roleid => $perm) {
3035                     if ($perm == CAP_PROHIBIT) {
3036                         $access[$roleid] = CAP_PROHIBIT;
3037                         continue;
3038                     }
3039                     if (!isset($access[$roleid])) {
3040                         $access[$roleid] = (int)$perm;
3041                     }
3042                 }
3043             }
3044         }
3046         unset($defs);
3048         // make lists of roles that are needed and prohibited
3049         $needed     = array(); // one of these is enough
3050         $prohibited = array(); // must not have any of these
3051         foreach ($access as $roleid => $perm) {
3052             if ($perm == CAP_PROHIBIT) {
3053                 unset($needed[$roleid]);
3054                 $prohibited[$roleid] = true;
3055             } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
3056                 $needed[$roleid] = true;
3057             }
3058         }
3060         $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : null;
3061         $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : null;
3063         $nobody = false;
3065         if ($isfrontpage) {
3066             if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
3067                 $nobody = true;
3068             } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
3069                 // everybody not having prohibit has the capability
3070                 $needed = array();
3071             } else if (empty($needed)) {
3072                 $nobody = true;
3073             }
3074         } else {
3075             if (!empty($prohibited[$defaultuserroleid])) {
3076                 $nobody = true;
3077             } else if (!empty($needed[$defaultuserroleid])) {
3078                 // everybody not having prohibit has the capability
3079                 $needed = array();
3080             } else if (empty($needed)) {
3081                 $nobody = true;
3082             }
3083         }
3085         if ($nobody) {
3086             // nobody can match so return some SQL that does not return any results
3087             $wheres[] = "1 = 2";
3089         } else {
3091             if ($needed) {
3092                 $ctxids = implode(',', $contextids);
3093                 $roleids = implode(',', array_keys($needed));
3094                 $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))";
3095             }
3097             if ($prohibited) {
3098                 $ctxids = implode(',', $contextids);
3099                 $roleids = implode(',', array_keys($prohibited));
3100                 $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))";
3101                 $wheres[] = "{$prefix}ra4.id IS NULL";
3102             }
3104             if ($groupid) {
3105                 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
3106                 $params["{$prefix}gmid"] = $groupid;
3107             }
3108         }
3110     } else {
3111         if ($groupid) {
3112             $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
3113             $params["{$prefix}gmid"] = $groupid;
3114         }
3115     }
3117     $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
3118     $params["{$prefix}guestid"] = $CFG->siteguest;
3120     if ($isfrontpage) {
3121         // all users are "enrolled" on the frontpage
3122     } else {
3123         $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
3124         $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
3125         $params[$prefix.'courseid'] = $coursecontext->instanceid;
3127         if ($onlyactive) {
3128             $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
3129             $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
3130             $now = round(time(), -2); // rounding helps caching in DB
3131             $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
3132                                                  $prefix.'active'=>ENROL_USER_ACTIVE,
3133                                                  $prefix.'now1'=>$now, $prefix.'now2'=>$now));
3134         }
3135     }
3137     $joins = implode("\n", $joins);
3138     $wheres = "WHERE ".implode(" AND ", $wheres);
3140     $sql = "SELECT DISTINCT {$prefix}u.id
3141                FROM {user} {$prefix}u
3142              $joins
3143             $wheres";
3145     return array($sql, $params);
3148 /**
3149  * Returns list of users enrolled into course.
3150  * @param object $context
3151  * @param string $withcapability
3152  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3153  * @param string $userfields requested user record fields
3154  * @param string $orderby
3155  * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
3156  * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
3157  * @return array of user records
3158  */
3159 function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
3160     global $DB;
3162     list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3163     $sql = "SELECT $userfields
3164               FROM {user} u
3165               JOIN ($esql) je ON je.id = u.id
3166              WHERE u.deleted = 0";
3168     if ($orderby) {
3169         $sql = "$sql ORDER BY $orderby";
3170     } else {
3171         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
3172     }
3174     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3177 /**
3178  * Counts list of users enrolled into course (as per above function)
3179  * @param object $context
3180  * @param string $withcapability
3181  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3182  * @return array of user records
3183  */
3184 function count_enrolled_users($context, $withcapability = '', $groupid = 0) {
3185     global $DB;
3187     list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3188     $sql = "SELECT count(u.id)
3189               FROM {user} u
3190               JOIN ($esql) je ON je.id = u.id
3191              WHERE u.deleted = 0";
3193     return $DB->count_records_sql($sql, $params);
3197 /**
3198  * Loads the capability definitions for the component (from file).
3199  *
3200  * Loads the capability definitions for the component (from file). If no
3201  * capabilities are defined for the component, we simply return an empty array.
3202  *
3203  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
3204  * @return array array of capabilities
3205  */
3206 function load_capability_def($component) {
3207     $defpath = get_component_directory($component).'/db/access.php';
3209     $capabilities = array();
3210     if (file_exists($defpath)) {
3211         require($defpath);
3212         if (!empty(${$component.'_capabilities'})) {
3213             // BC capability array name
3214             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
3215             debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files');
3216             $capabilities = ${$component.'_capabilities'};
3217         }
3218     }
3220     return $capabilities;
3224 /**
3225  * Gets the capabilities that have been cached in the database for this component.
3226  * @param string $component - examples: 'moodle', 'mod_forum'
3227  * @return array array of capabilities
3228  */
3229 function get_cached_capabilities($component = 'moodle') {
3230     global $DB;
3231     return $DB->get_records('capabilities', array('component'=>$component));
3234 /**
3235  * Returns default capabilities for given role archetype.
3236  * @param string $archetype role archetype
3237  * @return array
3238  */
3239 function get_default_capabilities($archetype) {
3240     global $DB;
3242     if (!$archetype) {
3243         return array();
3244     }
3246     $alldefs = array();
3247     $defaults = array();
3248     $components = array();
3249     $allcaps = $DB->get_records('capabilities');
3251     foreach ($allcaps as $cap) {
3252         if (!in_array($cap->component, $components)) {
3253             $components[] = $cap->component;
3254             $alldefs = array_merge($alldefs, load_capability_def($cap->component));
3255         }
3256     }
3257     foreach($alldefs as $name=>$def) {
3258         // Use array 'archetypes if available. Only if not specified, use 'legacy'.
3259         if (isset($def['archetypes'])) {
3260             if (isset($def['archetypes'][$archetype])) {
3261                 $defaults[$name] = $def['archetypes'][$archetype];
3262             }
3263         // 'legacy' is for backward compatibility with 1.9 access.php
3264         } else {
3265             if (isset($def['legacy'][$archetype])) {
3266                 $defaults[$name] = $def['legacy'][$archetype];
3267             }
3268         }
3269     }
3271     return $defaults;
3274 /**
3275  * Reset role capabilities to default according to selected role archetype.
3276  * If no archetype selected, removes all capabilities.
3277  * @param int $roleid
3278  * @return void
3279  */
3280 function reset_role_capabilities($roleid) {
3281     global $DB;
3283     $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
3284     $defaultcaps = get_default_capabilities($role->archetype);
3286     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
3288     $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
3290     foreach($defaultcaps as $cap=>$permission) {
3291         assign_capability($cap, $permission, $roleid, $sitecontext->id);
3292     }
3295 /**
3296  * Updates the capabilities table with the component capability definitions.
3297  * If no parameters are given, the function updates the core moodle
3298  * capabilities.
3299  *
3300  * Note that the absence of the db/access.php capabilities definition file
3301  * will cause any stored capabilities for the component to be removed from
3302  * the database.
3303  *
3304  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
3305  * @return boolean true if success, exception in case of any problems
3306  */
3307 function update_capabilities($component = 'moodle') {
3308     global $DB, $OUTPUT, $ACCESSLIB_PRIVATE;
3310     $storedcaps = array();
3312     $filecaps = load_capability_def($component);
3313     $cachedcaps = get_cached_capabilities($component);
3314     if ($cachedcaps) {
3315         foreach ($cachedcaps as $cachedcap) {
3316             array_push($storedcaps, $cachedcap->name);
3317             // update risk bitmasks and context levels in existing capabilities if needed
3318             if (array_key_exists($cachedcap->name, $filecaps)) {
3319                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
3320                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
3321                 }
3322                 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
3323                     $updatecap = new stdClass();
3324                     $updatecap->id = $cachedcap->id;
3325                     $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
3326                     $DB->update_record('capabilities', $updatecap);
3327                 }
3328                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
3329                     $updatecap = new stdClass();
3330                     $updatecap->id = $cachedcap->id;
3331                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
3332                     $DB->update_record('capabilities', $updatecap);
3333                 }
3335                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
3336                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
3337                 }
3338                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
3339                     $updatecap = new stdClass();
3340                     $updatecap->id = $cachedcap->id;
3341                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
3342                     $DB->update_record('capabilities', $updatecap);
3343                 }
3344             }
3345         }
3346     }
3348     // Are there new capabilities in the file definition?
3349     $newcaps = array();
3351     foreach ($filecaps as $filecap => $def) {
3352         if (!$storedcaps ||
3353                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3354             if (!array_key_exists('riskbitmask', $def)) {
3355                 $def['riskbitmask'] = 0; // no risk if not specified
3356             }
3357             $newcaps[$filecap] = $def;
3358         }
3359     }
3360     // Add new capabilities to the stored definition.
3361     foreach ($newcaps as $capname => $capdef) {
3362         $capability = new stdClass();
3363         $capability->name         = $capname;
3364         $capability->captype      = $capdef['captype'];
3365         $capability->contextlevel = $capdef['contextlevel'];
3366         $capability->component    = $component;
3367         $capability->riskbitmask  = $capdef['riskbitmask'];
3369         $DB->insert_record('capabilities', $capability, false);
3371         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3372             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
3373                 foreach ($rolecapabilities as $rolecapability){
3374                     //assign_capability will update rather than insert if capability exists
3375                     if (!assign_capability($capname, $rolecapability->permission,
3376                                             $rolecapability->roleid, $rolecapability->contextid, true)){
3377                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
3378                     }
3379                 }
3380             }
3381         // we ignore archetype key if we have cloned permissions
3382         } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
3383             assign_legacy_capabilities($capname, $capdef['archetypes']);
3384         // 'legacy' is for backward compatibility with 1.9 access.php
3385         } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
3386             assign_legacy_capabilities($capname, $capdef['legacy']);
3387         }
3388     }
3389     // Are there any capabilities that have been removed from the file
3390     // definition that we need to delete from the stored capabilities and
3391     // role assignments?
3392     capabilities_cleanup($component, $filecaps);
3394     // reset static caches
3395     $ACCESSLIB_PRIVATE->capabilities = null;
3397     return true;
3401 /**
3402  * Deletes cached capabilities that are no longer needed by the component.
3403  * Also unassigns these capabilities from any roles that have them.
3404  *
3405  * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
3406  * @param array $newcapdef array of the new capability definitions that will be
3407  *                     compared with the cached capabilities
3408  * @return int number of deprecated capabilities that have been removed
3409  */
3410 function capabilities_cleanup($component, $newcapdef = null) {
3411     global $DB;
3413     $removedcount = 0;
3415     if ($cachedcaps = get_cached_capabilities($component)) {
3416         foreach ($cachedcaps as $cachedcap) {
3417             if (empty($newcapdef) ||
3418                         array_key_exists($cachedcap->name, $newcapdef) === false) {
3420                 // Remove from capabilities cache.
3421                 $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
3422                 $removedcount++;
3423                 // Delete from roles.
3424                 if ($roles = get_roles_with_capability($cachedcap->name)) {
3425                     foreach($roles as $role) {
3426                         if (!unassign_capability($cachedcap->name, $role->id)) {
3427                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
3428                         }
3429                     }
3430                 }
3431             } // End if.
3432         }
3433     }
3434     return $removedcount;
3439 //////////////////
3440 // UI FUNCTIONS //
3441 //////////////////
3443 /**
3444  * @param integer $contextlevel $context->context level. One of the CONTEXT_... constants.
3445  * @return string the name for this type of context.
3446  */
3447 function get_contextlevel_name($contextlevel) {
3448     static $strcontextlevels = null;
3449     if (is_null($strcontextlevels)) {
3450         $strcontextlevels = array(
3451             CONTEXT_SYSTEM    => get_string('coresystem'),
3452             CONTEXT_USER      => get_string('user'),
3453             CONTEXT_COURSECAT => get_string('category'),
3454             CONTEXT_COURSE    => get_string('course'),
3455             CONTEXT_MODULE    => get_string('activitymodule'),
3456             CONTEXT_BLOCK     => get_string('block')
3457         );
3458     }
3459     return $strcontextlevels[$contextlevel];
3462 /**
3463  * Prints human readable context identifier.
3464  *
3465  * @param object $context the context.
3466  * @param boolean $withprefix whether to prefix the name of the context with the
3467  *      type of context, e.g. User, Course, Forum, etc.
3468  * @param boolean $short whether to user the short name of the thing. Only applies
3469  *      to course contexts
3470  * @return string the human readable context name.
3471  */
3472 function print_context_name($context, $withprefix = true, $short = false) {
3473     global $DB;
3475     $name = '';
3476     switch ($context->contextlevel) {
3478         case CONTEXT_SYSTEM:
3479             $name = get_string('coresystem');
3480             break;
3482         case CONTEXT_USER:
3483             if ($user = $DB->get_record('user', array('id'=>$context->instanceid))) {
3484                 if ($withprefix){
3485                     $name = get_string('user').': ';
3486                 }
3487                 $name .= fullname($user);
3488             }
3489             break;
3491         case CONTEXT_COURSECAT:
3492             if ($category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) {
3493                 if ($withprefix){
3494                     $name = get_string('category').': ';
3495                 }
3496                 $name .=format_string($category->name);
3497             }
3498             break;
3500         case CONTEXT_COURSE:
3501             if ($context->instanceid == SITEID) {
3502                 $name = get_string('frontpage', 'admin');
3503             } else {
3504                 if ($course = $DB->get_record('course', array('id'=>$context->instanceid))) {
3505                     if ($withprefix){
3506                         $name = get_string('course').': ';
3507                     }
3508                     if ($short){
3509                         $name .= format_string($course->shortname);
3510                     } else {
3511                         $name .= format_string($course->fullname);
3512                    }
3513                 }
3514             }
3515             break;
3517         case CONTEXT_MODULE:
3518             if ($cm = $DB->get_record_sql('SELECT cm.*, md.name AS modname FROM {course_modules} cm ' .
3519                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3520                 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
3521                         if ($withprefix){
3522                         $name = get_string('modulename', $cm->modname).': ';
3523                         }
3524                         $name .= $mod->name;
3525                     }
3526                 }
3527             break;
3529         case CONTEXT_BLOCK:
3530             if ($blockinstance = $DB->get_record('block_instances', array('id'=>$context->instanceid))) {
3531                 global $CFG;
3532                 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3533                 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
3534                 $blockname = "block_$blockinstance->blockname";
3535                 if ($blockobject = new $blockname()) {
3536                     if ($withprefix){
3537                         $name = get_string('block').': ';
3538                     }
3539                     $name .= $blockobject->title;
3540                 }
3541             }
3542             break;
3544         default:
3545             print_error('unknowncontext');
3546             return false;
3547     }
3549     return $name;
3552 /**
3553  * Get a URL for a context, if there is a natural one. For example, for
3554  * CONTEXT_COURSE, this is the course page. For CONTEXT_USER it is the
3555  * user profile page.
3556  *
3557  * @param object $context the context.
3558  * @return moodle_url
3559  */
3560 function get_context_url($context) {
3561     global $COURSE, $DB;
3563     switch ($context->contextlevel) {
3564         case CONTEXT_USER:
3565             if ($COURSE->id == SITEID) {
3566                 $url = new moodle_url('/user/profile.php', array('id'=>$context->instanceid));
3567             } else {
3568                 $url = new moodle_url('/user/view.php', array('id'=>$context->instanceid, 'courseid'=>$COURSE->id));
3569             }
3570             return $url;;
3572         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
3573             return new moodle_url('/course/category.php', array('id'=>$context->instanceid));
3575         case CONTEXT_COURSE: // 1 to 1 to course cat
3576             if ($context->instanceid != SITEID) {
3577                 return new moodle_url('/course/view.php', array('id'=>$context->instanceid));
3578             }
3579             break;
3581         case CONTEXT_MODULE: // 1 to 1 to course
3582             if ($modname = $DB->get_field_sql('SELECT md.name AS modname FROM {course_modules} cm ' .
3583                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3584                 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$context->instanceid));
3585             }
3586             break;
3588         case CONTEXT_BLOCK:
3589             $parentcontexts = get_parent_contexts($context, false);
3590             $parent = reset($parentcontexts);
3591             $parent = get_context_instance_by_id($parent);
3592             return get_context_url($parent);
3593     }
3595     return new moodle_url('/');
3598 /**
3599  * Returns an array of all the known types of risk
3600  * The array keys can be used, for example as CSS class names, or in calls to
3601  * print_risk_icon. The values are the corresponding RISK_ constants.
3602  *
3603  * @return array all the known types of risk.
3604  */
3605 function get_all_risks() {
3606     return array(
3607         'riskmanagetrust' => RISK_MANAGETRUST,
3608         'riskconfig'      => RISK_CONFIG,
3609         'riskxss'         => RISK_XSS,
3610         'riskpersonal'    => RISK_PERSONAL,
3611         'riskspam'        => RISK_SPAM,
3612         'riskdataloss'    => RISK_DATALOSS,
3613     );
3616 /**
3617  * Return a link to moodle docs for a given capability name
3618  *
3619  * @param object $capability a capability - a row from the mdl_capabilities table.
3620  * @return string the human-readable capability name as a link to Moodle Docs.
3621  */
3622 function get_capability_docs_link($capability) {
3623     global $CFG;
3624     $url = get_docs_url('Capabilities/' . $capability->name);
3625     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
3628 /**
3629  * Extracts the relevant capabilities given a contextid.
3630  * All case based, example an instance of forum context.
3631  * Will fetch all forum related capabilities, while course contexts
3632  * Will fetch all capabilities
3633  *
3634  * capabilities
3635  * `name` varchar(150) NOT NULL,