MDL-23400 accesslib - get_assignable_roles() now accepts $user param
[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   moodlecore
122  * @copyright 1999 onwards Martin Dougiamas  http://dougiamas.com
123  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
124  */
126 /** permission definitions */
127 define('CAP_INHERIT', 0);
128 /** permission definitions */
129 define('CAP_ALLOW', 1);
130 /** permission definitions */
131 define('CAP_PREVENT', -1);
132 /** permission definitions */
133 define('CAP_PROHIBIT', -1000);
135 /** context definitions */
136 define('CONTEXT_SYSTEM', 10);
137 /** context definitions */
138 define('CONTEXT_USER', 30);
139 /** context definitions */
140 define('CONTEXT_COURSECAT', 40);
141 /** context definitions */
142 define('CONTEXT_COURSE', 50);
143 /** context definitions */
144 define('CONTEXT_MODULE', 70);
145 /** context definitions */
146 define('CONTEXT_BLOCK', 80);
148 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
149 define('RISK_MANAGETRUST', 0x0001);
150 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
151 define('RISK_CONFIG',      0x0002);
152 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
153 define('RISK_XSS',         0x0004);
154 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
155 define('RISK_PERSONAL',    0x0008);
156 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
157 define('RISK_SPAM',        0x0010);
158 /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */
159 define('RISK_DATALOSS',    0x0020);
161 /** rolename displays - the name as defined in the role definition */
162 define('ROLENAME_ORIGINAL', 0);
163 /** rolename displays - the name as defined by a role alias */
164 define('ROLENAME_ALIAS', 1);
165 /** rolename displays - Both, like this:  Role alias (Original)*/
166 define('ROLENAME_BOTH', 2);
167 /** rolename displays - the name as defined in the role definition and the shortname in brackets*/
168 define('ROLENAME_ORIGINALANDSHORT', 3);
169 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing*/
170 define('ROLENAME_ALIAS_RAW', 4);
171 /** rolename displays - the name is simply short role name*/
172 define('ROLENAME_SHORT', 5);
174 /** size limit for context cache */
175 if (!defined('MAX_CONTEXT_CACHE_SIZE')) {
176     define('MAX_CONTEXT_CACHE_SIZE', 5000);
179 /**
180  * Although this looks like a global variable, it isn't really.
181  *
182  * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
183  * It is used to cache various bits of data between function calls for performance reasons.
184  * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
185  * as methods of a class, instead of functions.
186  *
187  * @global stdClass $ACCESSLIB_PRIVATE
188  * @name $ACCESSLIB_PRIVATE
189  */
190 $ACCESSLIB_PRIVATE = new stdClass;
191 $ACCESSLIB_PRIVATE->contexts = array(); // Cache of context objects by level and instance
192 $ACCESSLIB_PRIVATE->contextsbyid = array(); // Cache of context objects by id
193 $ACCESSLIB_PRIVATE->systemcontext = NULL; // Used in get_system_context
194 $ACCESSLIB_PRIVATE->dirtycontexts = NULL; // Dirty contexts cache
195 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER
196 $ACCESSLIB_PRIVATE->roledefinitions = array(); // role definitions cache - helps a lot with mem usage in cron
197 $ACCESSLIB_PRIVATE->croncache = array(); // Used in get_role_access
198 $ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts.
199 $ACCESSLIB_PRIVATE->capabilities = NULL; // detailed information about the capabilities
201 /**
202  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
203  *
204  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
205  * accesslib's private caches. You need to do this before setting up test data,
206  * and also at the end of the tests.
207  * @global object
208  * @global object
209  * @global object
210  */
211 function accesslib_clear_all_caches_for_unit_testing() {
212     global $UNITTEST, $USER, $ACCESSLIB_PRIVATE;
213     if (empty($UNITTEST->running)) {
214         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
215     }
216     $ACCESSLIB_PRIVATE->contexts = array();
217     $ACCESSLIB_PRIVATE->contextsbyid = array();
218     $ACCESSLIB_PRIVATE->systemcontext = NULL;
219     $ACCESSLIB_PRIVATE->dirtycontexts = NULL;
220     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
221     $ACCESSLIB_PRIVATE->roledefinitions = array();
222     $ACCESSLIB_PRIVATE->croncache = array();
223     $ACCESSLIB_PRIVATE->preloadedcourses = array();
224     $ACCESSLIB_PRIVATE->capabilities = NULL;
226     unset($USER->access);
229 /**
230  * Private function. Add a context object to accesslib's caches.
231  * @global object
232  * @param object $context
233  */
234 function cache_context($context) {
235     global $ACCESSLIB_PRIVATE;
237     // If there are too many items in the cache already, remove items until
238     // there is space
239     while (count($ACCESSLIB_PRIVATE->contextsbyid) >= MAX_CONTEXT_CACHE_SIZE) {
240         $first = reset($ACCESSLIB_PRIVATE->contextsbyid);
241         unset($ACCESSLIB_PRIVATE->contextsbyid[$first->id]);
242         unset($ACCESSLIB_PRIVATE->contexts[$first->contextlevel][$first->instanceid]);
243     }
245     $ACCESSLIB_PRIVATE->contexts[$context->contextlevel][$context->instanceid] = $context;
246     $ACCESSLIB_PRIVATE->contextsbyid[$context->id] = $context;
249 /**
250  * This is really slow!!! do not use above course context level
251  *
252  * @global object
253  * @param int $roleid
254  * @param object $context
255  * @return array
256  */
257 function get_role_context_caps($roleid, $context) {
258     global $DB;
260     //this is really slow!!!! - do not use above course context level!
261     $result = array();
262     $result[$context->id] = array();
264     // first emulate the parent context capabilities merging into context
265     $searchcontexts = array_reverse(get_parent_contexts($context));
266     array_push($searchcontexts, $context->id);
267     foreach ($searchcontexts as $cid) {
268         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
269             foreach ($capabilities as $cap) {
270                 if (!array_key_exists($cap->capability, $result[$context->id])) {
271                     $result[$context->id][$cap->capability] = 0;
272                 }
273                 $result[$context->id][$cap->capability] += $cap->permission;
274             }
275         }
276     }
278     // now go through the contexts bellow given context
279     $searchcontexts = array_keys(get_child_contexts($context));
280     foreach ($searchcontexts as $cid) {
281         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
282             foreach ($capabilities as $cap) {
283                 if (!array_key_exists($cap->contextid, $result)) {
284                     $result[$cap->contextid] = array();
285                 }
286                 $result[$cap->contextid][$cap->capability] = $cap->permission;
287             }
288         }
289     }
291     return $result;
294 /**
295  * Gets the accessdata for role "sitewide" (system down to course)
296  *
297  * @global object
298  * @global object
299  * @param int $roleid
300  * @param array $accessdata defaults to NULL
301  * @return array
302  */
303 function get_role_access($roleid, $accessdata=NULL) {
305     global $CFG, $DB;
307     /* Get it in 1 cheap DB query...
308      * - relevant role caps at the root and down
309      *   to the course level - but not below
310      */
311     if (is_null($accessdata)) {
312         $accessdata           = array(); // named list
313         $accessdata['ra']     = array();
314         $accessdata['rdef']   = array();
315         $accessdata['loaded'] = array();
316     }
318     //
319     // Overrides for the role IN ANY CONTEXTS
320     // down to COURSE - not below -
321     //
322     $sql = "SELECT ctx.path,
323                    rc.capability, rc.permission
324               FROM {context} ctx
325               JOIN {role_capabilities} rc
326                    ON rc.contextid=ctx.id
327              WHERE rc.roleid = ?
328                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
329           ORDER BY ctx.depth, ctx.path";
330     $params = array($roleid);
332     // we need extra caching in CLI scripts and cron
333     if (CLI_SCRIPT) {
334         global $ACCESSLIB_PRIVATE;
336         if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) {
337             $ACCESSLIB_PRIVATE->croncache[$roleid] = array();
338             if ($rs = $DB->get_recordset_sql($sql, $params)) {
339                 foreach ($rs as $rd) {
340                     $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd;
341                 }
342                 $rs->close();
343             }
344         }
346         foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) {
347             $k = "{$rd->path}:{$roleid}";
348             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
349         }
351     } else {
352         if ($rs = $DB->get_recordset_sql($sql, $params)) {
353             foreach ($rs as $rd) {
354                 $k = "{$rd->path}:{$roleid}";
355                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
356             }
357             unset($rd);
358             $rs->close();
359         }
360     }
362     return $accessdata;
365 /**
366  * Gets the accessdata for role "sitewide" (system down to course)
367  *
368  * @global object
369  * @global object
370  * @param int $roleid
371  * @param array $accessdata defaults to NULL
372  * @return array
373  */
374 function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
376     global $CFG, $DB;
378     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
379     $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
381     //
382     // Overrides for the role in any contexts related to the course
383     //
384     $sql = "SELECT ctx.path,
385                    rc.capability, rc.permission
386               FROM {context} ctx
387               JOIN {role_capabilities} rc
388                    ON rc.contextid=ctx.id
389              WHERE rc.roleid = ?
390                    AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?)
391                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
392           ORDER BY ctx.depth, ctx.path";
393     $params = array($roleid, "$base/%");
395     if ($rs = $DB->get_recordset_sql($sql, $params)) {
396         foreach ($rs as $rd) {
397             $k = "{$rd->path}:{$roleid}";
398             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
399         }
400         unset($rd);
401         $rs->close();
402     }
404     return $accessdata;
408 /**
409  * Get the default guest role
410  *
411  * @global object
412  * @global object
413  * @return object role
414  */
415 function get_guest_role() {
416     global $CFG, $DB;
418     if (empty($CFG->guestroleid)) {
419         if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
420             $guestrole = array_shift($roles);   // Pick the first one
421             set_config('guestroleid', $guestrole->id);
422             return $guestrole;
423         } else {
424             debugging('Can not find any guest role!');
425             return false;
426         }
427     } else {
428         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
429             return $guestrole;
430         } else {
431             //somebody is messing with guest roles, remove incorrect setting and try to find a new one
432             set_config('guestroleid', '');
433             return get_guest_role();
434         }
435     }
438 /**
439  * Check whether a user has a particular capability in a given context.
440  *
441  * For example::
442  *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
443  *      has_capability('mod/forum:replypost',$context)
444  *
445  * By default checks the capabilities of the current user, but you can pass a
446  * different userid. By default will return true for admin users, but you can override that with the fourth argument.
447  *
448  * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
449  * or capabilities with XSS, config or data loss risks.
450  *
451  * @param string $capability the name of the capability to check. For example mod/forum:view
452  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
453  * @param integer|object $user A user id or object. By default (NULL) checks the permissions of the current user.
454  * @param boolean $doanything If false, ignores effect of admin role assignment
455  * @return boolean true if the user has this capability. Otherwise false.
456  */
457 function has_capability($capability, $context, $user = NULL, $doanything=true) {
458     global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
460     if (during_initial_install()) {
461         if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") {
462             // we are in an installer - roles can not work yet
463             return true;
464         } else {
465             return false;
466         }
467     }
469     if (strpos($capability, 'moodle/legacy:') === 0) {
470         throw new coding_exception('Legacy capabilities can not be used any more!');
471     }
473     // the original $CONTEXT here was hiding serious errors
474     // for security reasons do not reuse previous context
475     if (empty($context)) {
476         debugging('Incorrect context specified');
477         return false;
478     }
479     if (!is_bool($doanything)) {
480         throw new coding_exception('Capability parameter "doanything" is wierd ("'.$doanything.'"). This has to be fixed in code.');
481     }
483     // make sure there is a real user specified
484     if ($user === NULL) {
485         $userid = !empty($USER->id) ? $USER->id : 0;
486     } else {
487         $userid = !empty($user->id) ? $user->id : $user;
488     }
490     // capability must exist
491     if (!$capinfo = get_capability_info($capability)) {
492         debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
493         return false;
494     }
495     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
496     if (($capinfo->captype === 'write') or ((int)$capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
497         if (isguestuser($userid) or $userid == 0) {
498             return false;
499         }
500     }
502     if (is_null($context->path) or $context->depth == 0) {
503         //this should not happen
504         $contexts = array(SYSCONTEXTID, $context->id);
505         $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
506         debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
508     } else {
509         $contexts = explode('/', $context->path);
510         array_shift($contexts);
511     }
513     if (CLI_SCRIPT && !isset($USER->access)) {
514         // In cron, some modules setup a 'fake' $USER,
515         // ensure we load the appropriate accessdata.
516         if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
517             $ACCESSLIB_PRIVATE->dirtycontexts = NULL; //load fresh dirty contexts
518         } else {
519             load_user_accessdata($userid);
520             $ACCESSLIB_PRIVATE->dirtycontexts = array();
521         }
522         $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
524     } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) {
525         // caps not loaded yet - better to load them to keep BC with 1.8
526         // not-logged-in user or $USER object set up manually first time here
527         load_all_capabilities();
528         $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now
529         $ACCESSLIB_PRIVATE->roledefinitions = array();
530     }
532     // Load dirty contexts list if needed
533     if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
534         if (isset($USER->access['time'])) {
535             $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
536         }
537         else {
538             $ACCESSLIB_PRIVATE->dirtycontexts = array();
539         }
540     }
542     // Careful check for staleness...
543     if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
544         // reload all capabilities - preserving loginas, roleswitches, etc
545         // and then cleanup any marks of dirtyness... at least from our short
546         // term memory! :-)
547         $ACCESSLIB_PRIVATE->accessdatabyuser = array();
548         $ACCESSLIB_PRIVATE->roledefinitions = array();
550         if (CLI_SCRIPT) {
551             load_user_accessdata($userid);
552             $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
553             $ACCESSLIB_PRIVATE->dirtycontexts = array();
555         } else {
556             reload_all_capabilities();
557         }
558     }
560     // Find out if user is admin - it is not possible to override the doanything in any way
561     // and it is not possible to switch to admin role either.
562     if ($doanything) {
563         if (is_siteadmin($userid)) {
564             if ($userid != $USER->id) {
565                 return true;
566             }
567             // make sure switchrole is not used in this context
568             if (empty($USER->access['rsw'])) {
569                 return true;
570             }
571             $parts = explode('/', trim($context->path, '/'));
572             $path = '';
573             $switched = false;
574             foreach ($parts as $part) {
575                 $path .= '/' . $part;
576                 if (!empty($USER->access['rsw'][$path])) {
577                     $switched = true;
578                     break;
579                 }
580             }
581             if (!$switched) {
582                 return true;
583             }
584             //ok, admin switched role in this context, let's use normal access control rules
585         }
586     }
588     // divulge how many times we are called
589     //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
591     if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid
592         //
593         // For the logged in user, we have $USER->access
594         // which will have all RAs and caps preloaded for
595         // course and above contexts.
596         //
597         // Contexts below courses && contexts that do not
598         // hang from courses are loaded into $USER->access
599         // on demand, and listed in $USER->access[loaded]
600         //
601         if ($context->contextlevel <= CONTEXT_COURSE) {
602             // Course and above are always preloaded
603             return has_capability_in_accessdata($capability, $context, $USER->access);
604         }
605         // Load accessdata for below-the-course contexts
606         if (!path_inaccessdata($context->path,$USER->access)) {
607             // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
608             // $bt = debug_backtrace();
609             // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
610             load_subcontext($USER->id, $context, $USER->access);
611         }
612         return has_capability_in_accessdata($capability, $context, $USER->access);
613     }
615     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
616         load_user_accessdata($userid);
617     }
619     if ($context->contextlevel <= CONTEXT_COURSE) {
620         // Course and above are always preloaded
621         return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
622     }
623     // Load accessdata for below-the-course contexts as needed
624     if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
625         // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
626         // $bt = debug_backtrace();
627         // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
628         load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
629     }
630     return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
633 /**
634  * Check if the user has any one of several capabilities from a list.
635  *
636  * This is just a utility method that calls has_capability in a loop. Try to put
637  * the capabilities that most users are likely to have first in the list for best
638  * performance.
639  *
640  * There are probably tricks that could be done to improve the performance here, for example,
641  * check the capabilities that are already cached first.
642  *
643  * @see has_capability()
644  * @param array $capabilities an array of capability names.
645  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
646  * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
647  * @param boolean $doanything If false, ignore effect of admin role assignment
648  * @return boolean true if the user has any of these capabilities. Otherwise false.
649  */
650 function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
651     if (!is_array($capabilities)) {
652         debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array');
653         return false;
654     }
655     foreach ($capabilities as $capability) {
656         if (has_capability($capability, $context, $userid, $doanything)) {
657             return true;
658         }
659     }
660     return false;
663 /**
664  * Check if the user has all the capabilities in a list.
665  *
666  * This is just a utility method that calls has_capability in a loop. Try to put
667  * the capabilities that fewest users are likely to have first in the list for best
668  * performance.
669  *
670  * There are probably tricks that could be done to improve the performance here, for example,
671  * check the capabilities that are already cached first.
672  *
673  * @see has_capability()
674  * @param array $capabilities an array of capability names.
675  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
676  * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
677  * @param boolean $doanything If false, ignore effect of admin role assignment
678  * @return boolean true if the user has all of these capabilities. Otherwise false.
679  */
680 function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) {
681     if (!is_array($capabilities)) {
682         debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
683         return false;
684     }
685     foreach ($capabilities as $capability) {
686         if (!has_capability($capability, $context, $userid, $doanything)) {
687             return false;
688         }
689     }
690     return true;
693 /**
694  * Check if the user is an admin at the site level.
695  *
696  * Please note that use of proper capabilities is always encouraged,
697  * this function is supposed to be used from core or for temporary hacks.
698  *
699  * @param   int|object  $user_or_id user id or user object
700  * @returns bool true if user is one of the administrators, false otherwise
701  */
702 function is_siteadmin($user_or_id = NULL) {
703     global $CFG, $USER;
705     if ($user_or_id === NULL) {
706         $user_or_id = $USER;
707     }
709     if (empty($user_or_id)) {
710         return false;
711     }
712     if (!empty($user_or_id->id)) {
713         // we support
714         $userid = $user_or_id->id;
715     } else {
716         $userid = $user_or_id;
717     }
719     $siteadmins = explode(',', $CFG->siteadmins);
720     return in_array($userid, $siteadmins);
723 /**
724  * Returns true if user has at least one role assign
725  * of 'coursecontact' role (is potentially listed in some course descriptions).
726  * @param $userid
727  * @return unknown_type
728  */
729 function has_coursecontact_role($userid) {
730     global $DB;
732     if (empty($CFG->coursecontact)) {
733         return false;
734     }
735     $sql = "SELECT 1
736               FROM {role_assignments}
737              WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
738     return $DB->record_exists($sql, array('userid'=>$userid));
741 /**
742  * @param string $path
743  * @return string
744  */
745 function get_course_from_path($path) {
746     // assume that nothing is more than 1 course deep
747     if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
748         return $matches[1];
749     }
750     return false;
753 /**
754  * @param string $path
755  * @param array $accessdata
756  * @return bool
757  */
758 function path_inaccessdata($path, $accessdata) {
759     if (empty($accessdata['loaded'])) {
760         return false;
761     }
763     // assume that contexts hang from sys or from a course
764     // this will only work well with stuff that hangs from a course
765     if (in_array($path, $accessdata['loaded'], true)) {
766             // error_log("found it!");
767         return true;
768     }
769     $base = '/' . SYSCONTEXTID;
770     while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
771         $path = $matches[1];
772         if ($path === $base) {
773             return false;
774         }
775         if (in_array($path, $accessdata['loaded'], true)) {
776             return true;
777         }
778     }
779     return false;
782 /**
783  * Does the user have a capability to do something?
784  *
785  * Walk the accessdata array and return true/false.
786  * Deals with prohibits, roleswitching, aggregating
787  * capabilities, etc.
788  *
789  * The main feature of here is being FAST and with no
790  * side effects.
791  *
792  * Notes:
793  *
794  * Switch Roles exits early
795  * ------------------------
796  * cap checks within a switchrole need to exit early
797  * in our bottom up processing so they don't "see" that
798  * there are real RAs that can do all sorts of things.
799  *
800  * Switch Role merges with default role
801  * ------------------------------------
802  * If you are a teacher in course X, you have at least
803  * teacher-in-X + defaultloggedinuser-sitewide. So in the
804  * course you'll have techer+defaultloggedinuser.
805  * We try to mimic that in switchrole.
806  *
807  * Permission evaluation
808  * ---------------------
809  * Originally there was an extremely complicated way
810  * to determine the user access that dealt with
811  * "locality" or role assignments and role overrides.
812  * Now we simply evaluate access for each role separately
813  * and then verify if user has at least one role with allow
814  * and at the same time no role with prohibit.
815  *
816  * @param string $capability
817  * @param object $context
818  * @param array $accessdata
819  * @return bool
820  */
821 function has_capability_in_accessdata($capability, $context, array $accessdata) {
822     global $CFG;
824     if (empty($context->id)) {
825         throw new coding_exception('Invalid context specified');
826     }
828     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
829     $contextids = explode('/', trim($context->path, '/'));
830     $paths = array($context->path);
831     while ($contextids) {
832         array_pop($contextids);
833         $paths[] = '/' . implode('/', $contextids);
834     }
835     unset($contextids);
837     $roles = array();
838     $switchedrole = false;
840     // Find out if role switched
841     if (!empty($accessdata['rsw'])) {
842         // From the bottom up...
843         foreach ($paths as $path) {
844             if (isset($accessdata['rsw'][$path])) {
845                 // Found a switchrole assignment - check for that role _plus_ the default user role
846                 $roles = array($accessdata['rsw'][$path]=>NULL, $CFG->defaultuserroleid=>NULL);
847                 $switchedrole = true;
848                 break;
849             }
850         }
851     }
853     if (!$switchedrole) {
854         // get all users roles in this context and above
855         foreach ($paths as $path) {
856             if (isset($accessdata['ra'][$path])) {
857                 foreach ($accessdata['ra'][$path] as $roleid) {
858                     $roles[$roleid] = NULL;
859                 }
860             }
861         }
862     }
864     // Now find out what access is given to each role, going bottom-->up direction
865     foreach ($roles as $roleid => $ignored) {
866         foreach ($paths as $path) {
867             if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
868                 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
869                 if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) {
870                     $roles[$roleid] = $perm;
871                 }
872             }
873         }
874     }
875     // any CAP_PROHIBIT found means no permission for the user
876     if (array_search(CAP_PROHIBIT, $roles) !== false) {
877         return false;
878     }
880     // at least one CAP_ALLOW means the user has a permission
881     return (array_search(CAP_ALLOW, $roles) !== false);
884 /**
885  * @param object $context
886  * @param array $accessdata
887  * @return array
888  */
889 function aggregate_roles_from_accessdata($context, $accessdata) {
891     $path = $context->path;
893     // build $contexts as a list of "paths" of the current
894     // contexts and parents with the order top-to-bottom
895     $contexts = array($path);
896     while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
897         $path = $matches[1];
898         array_unshift($contexts, $path);
899     }
901     $cc = count($contexts);
903     $roles = array();
904     // From the bottom up...
905     for ($n=$cc-1;$n>=0;$n--) {
906         $ctxp = $contexts[$n];
907         if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
908             // Found assignments on this leaf
909             $addroles = $accessdata['ra'][$ctxp];
910             $roles    = array_merge($roles, $addroles);
911         }
912     }
914     return array_unique($roles);
917 /**
918  * A convenience function that tests has_capability, and displays an error if
919  * the user does not have that capability.
920  *
921  * NOTE before Moodle 2.0, this function attempted to make an appropriate
922  * require_login call before checking the capability. This is no longer the case.
923  * You must call require_login (or one of its variants) if you want to check the
924  * user is logged in, before you call this function.
925  *
926  * @see has_capability()
927  *
928  * @param string $capability the name of the capability to check. For example mod/forum:view
929  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
930  * @param integer $userid A user id. By default (NULL) checks the permissions of the current user.
931  * @param bool $doanything If false, ignore effect of admin role assignment
932  * @param string $errorstring The error string to to user. Defaults to 'nopermissions'.
933  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
934  * @return void terminates with an error if the user does not have the given capability.
935  */
936 function require_capability($capability, $context, $userid = NULL, $doanything = true,
937                             $errormessage = 'nopermissions', $stringfile = '') {
938     if (!has_capability($capability, $context, $userid, $doanything)) {
939         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
940     }
943 /**
944  * Get an array of courses where cap requested is available
945  * and user is enrolled, this can be relatively slow.
946  *
947  * @param string $capability - name of the capability
948  * @param array  $accessdata_ignored
949  * @param bool   $doanything_ignored
950  * @param string $sort - sorting fields - prefix each fieldname with "c."
951  * @param array  $fields - additional fields you are interested in...
952  * @param int    $limit_ignored
953  * @return array $courses - ordered array of course objects - see notes above
954  */
955 function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort='c.sortorder ASC', $fields=NULL, $limit_ignored=0) {
957     //TODO: this should be most probably deprecated
959     $courses = enrol_get_users_courses($userid, true, $fields, $sort);
960     foreach ($courses as $id=>$course) {
961         $context = get_context_instance(CONTEXT_COURSE, $id);
962         if (!has_capability($cap, $context, $userid)) {
963             unset($courses[$id]);
964         }
965     }
967     return $courses;
971 /**
972  * Return a nested array showing role assignments
973  * all relevant role capabilities for the user at
974  * site/course_category/course levels
975  *
976  * We do _not_ delve deeper than courses because the number of
977  * overrides at the module/block levels is HUGE.
978  *
979  * [ra]   => [/path/][]=roleid
980  * [rdef] => [/path/:roleid][capability]=permission
981  * [loaded] => array('/path', '/path')
982  *
983  * @global object
984  * @global object
985  * @param $userid integer - the id of the user
986  */
987 function get_user_access_sitewide($userid) {
989     global $CFG, $DB;
991     /* Get in 3 cheap DB queries...
992      * - role assignments
993      * - relevant role caps
994      *   - above and within this user's RAs
995      *   - below this user's RAs - limited to course level
996      */
998     $accessdata           = array(); // named list
999     $accessdata['ra']     = array();
1000     $accessdata['rdef']   = array();
1001     $accessdata['loaded'] = array();
1003     //
1004     // Role assignments
1005     //
1006     $sql = "SELECT ctx.path, ra.roleid
1007               FROM {role_assignments} ra
1008               JOIN {context} ctx ON ctx.id=ra.contextid
1009              WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE;
1010     $params = array($userid);
1011     $rs = $DB->get_recordset_sql($sql, $params);
1013     //
1014     // raparents collects paths & roles we need to walk up
1015     // the parenthood to build the rdef
1016     //
1017     $raparents = array();
1018     if ($rs) {
1019         foreach ($rs as $ra) {
1020             // RAs leafs are arrays to support multi
1021             // role assignments...
1022             if (!isset($accessdata['ra'][$ra->path])) {
1023                 $accessdata['ra'][$ra->path] = array();
1024             }
1025             array_push($accessdata['ra'][$ra->path], $ra->roleid);
1027             // Concatenate as string the whole path (all related context)
1028             // for this role. This is damn faster than using array_merge()
1029             // Will unique them later
1030             if (isset($raparents[$ra->roleid])) {
1031                 $raparents[$ra->roleid] .= $ra->path;
1032             } else {
1033                 $raparents[$ra->roleid] = $ra->path;
1034             }
1035         }
1036         unset($ra);
1037         $rs->close();
1038     }
1040     // Walk up the tree to grab all the roledefs
1041     // of interest to our user...
1042     //
1043     // NOTE: we use a series of IN clauses here - which
1044     // might explode on huge sites with very convoluted nesting of
1045     // categories... - extremely unlikely that the number of categories
1046     // and roletypes is so large that we hit the limits of IN()
1047     $clauses = '';
1048     $cparams = array();
1049     foreach ($raparents as $roleid=>$strcontexts) {
1050         $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/'))));
1051         if ($contexts ==! '') {
1052             if ($clauses) {
1053                 $clauses .= ' OR ';
1054             }
1055             $clauses .= "(roleid=? AND contextid IN ($contexts))";
1056             $cparams[] = $roleid;
1057         }
1058     }
1060     if ($clauses !== '') {
1061         $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1062                 FROM {role_capabilities} rc
1063                 JOIN {context} ctx
1064                   ON rc.contextid=ctx.id
1065                 WHERE $clauses";
1067         unset($clauses);
1068         $rs = $DB->get_recordset_sql($sql, $cparams);
1070         if ($rs) {
1071             foreach ($rs as $rd) {
1072                 $k = "{$rd->path}:{$rd->roleid}";
1073                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1074             }
1075             unset($rd);
1076             $rs->close();
1077         }
1078     }
1080     //
1081     // Overrides for the role assignments IN SUBCONTEXTS
1082     // (though we still do _not_ go below the course level.
1083     //
1084     // NOTE that the JOIN w sctx is with 3-way triangulation to
1085     // catch overrides to the applicable role in any subcontext, based
1086     // on the path field of the parent.
1087     //
1088     $sql = "SELECT sctx.path, ra.roleid,
1089                    ctx.path AS parentpath,
1090                    rco.capability, rco.permission
1091               FROM {role_assignments} ra
1092               JOIN {context} ctx
1093                    ON ra.contextid=ctx.id
1094               JOIN {context} sctx
1095                    ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " )
1096               JOIN {role_capabilities} rco
1097                    ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1098              WHERE ra.userid = ?
1099                AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
1100                AND sctx.contextlevel <= ".CONTEXT_COURSE."
1101           ORDER BY sctx.depth, sctx.path, ra.roleid";
1102     $params = array($userid);
1103     $rs = $DB->get_recordset_sql($sql, $params);
1104     if ($rs) {
1105         foreach ($rs as $rd) {
1106             $k = "{$rd->path}:{$rd->roleid}";
1107             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1108         }
1109         unset($rd);
1110         $rs->close();
1111     }
1112     return $accessdata;
1115 /**
1116  * Add to the access ctrl array the data needed by a user for a given context
1117  *
1118  * @global object
1119  * @global object
1120  * @param integer $userid the id of the user
1121  * @param object $context needs path!
1122  * @param array $accessdata accessdata array
1123  */
1124 function load_subcontext($userid, $context, &$accessdata) {
1126     global $CFG, $DB;
1128     /* Get the additional RAs and relevant rolecaps
1129      * - role assignments - with role_caps
1130      * - relevant role caps
1131      *   - above this user's RAs
1132      *   - below this user's RAs - limited to course level
1133      */
1135     $base = "/" . SYSCONTEXTID;
1137     //
1138     // Replace $context with the target context we will
1139     // load. Normally, this will be a course context, but
1140     // may be a different top-level context.
1141     //
1142     // We have 3 cases
1143     //
1144     // - Course
1145     // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1146     // - BLOCK/MODULE/GROUP hanging from a course
1147     //
1148     // For course contexts, we _already_ have the RAs
1149     // but the cost of re-fetching is minimal so we don't care.
1150     //
1151     if ($context->contextlevel !== CONTEXT_COURSE
1152         && $context->path !== "$base/{$context->id}") {
1153         // Case BLOCK/MODULE/GROUP hanging from a course
1154         // Assumption: the course _must_ be our parent
1155         // If we ever see stuff nested further this needs to
1156         // change to do 1 query over the exploded path to
1157         // find out which one is the course
1158         $courses = explode('/',get_course_from_path($context->path));
1159         $targetid = array_pop($courses);
1160         $context = get_context_instance_by_id($targetid);
1162     }
1164     //
1165     // Role assignments in the context and below
1166     //
1167     $sql = "SELECT ctx.path, ra.roleid
1168               FROM {role_assignments} ra
1169               JOIN {context} ctx
1170                    ON ra.contextid=ctx.id
1171              WHERE ra.userid = ?
1172                    AND (ctx.path = ? OR ctx.path LIKE ?)
1173           ORDER BY ctx.depth, ctx.path, ra.roleid";
1174     $params = array($userid, $context->path, $context->path."/%");
1175     $rs = $DB->get_recordset_sql($sql, $params);
1177     //
1178     // Read in the RAs, preventing duplicates
1179     //
1180     if ($rs) {
1181         $localroles = array();
1182         $lastseen  = '';
1183         foreach ($rs as $ra) {
1184             if (!isset($accessdata['ra'][$ra->path])) {
1185                 $accessdata['ra'][$ra->path] = array();
1186             }
1187             // only add if is not a repeat caused
1188             // by capability join...
1189             // (this check is cheaper than in_array())
1190             if ($lastseen !== $ra->path.':'.$ra->roleid) {
1191                 $lastseen = $ra->path.':'.$ra->roleid;
1192                 array_push($accessdata['ra'][$ra->path], $ra->roleid);
1193                 array_push($localroles,           $ra->roleid);
1194             }
1195         }
1196         $rs->close();
1197     }
1199     //
1200     // Walk up and down the tree to grab all the roledefs
1201     // of interest to our user...
1202     //
1203     // NOTES
1204     // - we use IN() but the number of roles is very limited.
1205     //
1206     $courseroles    = aggregate_roles_from_accessdata($context, $accessdata);
1208     // Do we have any interesting "local" roles?
1209     $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1210     $wherelocalroles='';
1211     if (count($localroles)) {
1212         // Role defs for local roles in 'higher' contexts...
1213         $contexts = substr($context->path, 1); // kill leading slash
1214         $contexts = str_replace('/', ',', $contexts);
1215         $localroleids = implode(',',$localroles);
1216         $wherelocalroles="OR (rc.roleid IN ({$localroleids})
1217                               AND ctx.id IN ($contexts))" ;
1218     }
1220     // We will want overrides for all of them
1221     $whereroles = '';
1222     if ($roleids  = implode(',',array_merge($courseroles,$localroles))) {
1223         $whereroles = "rc.roleid IN ($roleids) AND";
1224     }
1225     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1226               FROM {role_capabilities} rc
1227               JOIN {context} ctx
1228                    ON rc.contextid=ctx.id
1229              WHERE ($whereroles
1230                     (ctx.id=? OR ctx.path LIKE ?))
1231                    $wherelocalroles
1232           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1233     $params = array($context->id, $context->path."/%");
1235     $newrdefs = array();
1236     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1237         foreach ($rs as $rd) {
1238             $k = "{$rd->path}:{$rd->roleid}";
1239             if (!array_key_exists($k, $newrdefs)) {
1240                 $newrdefs[$k] = array();
1241             }
1242             $newrdefs[$k][$rd->capability] = $rd->permission;
1243         }
1244         $rs->close();
1245     } else {
1246         debugging('Bad SQL encountered!');
1247     }
1249     compact_rdefs($newrdefs);
1250     foreach ($newrdefs as $key=>$value) {
1251         $accessdata['rdef'][$key] =& $newrdefs[$key];
1252     }
1254     // error_log("loaded {$context->path}");
1255     $accessdata['loaded'][] = $context->path;
1258 /**
1259  * Add to the access ctrl array the data needed by a role for a given context.
1260  *
1261  * The data is added in the rdef key.
1262  *
1263  * This role-centric function is useful for role_switching
1264  * and to get an overview of what a role gets under a
1265  * given context and below...
1266  *
1267  * @global object
1268  * @global object
1269  * @param integer $roleid the id of the user
1270  * @param object $context needs path!
1271  * @param array $accessdata accessdata array NULL by default
1272  * @return array
1273  */
1274 function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
1276     global $CFG, $DB;
1278     /* Get the relevant rolecaps into rdef
1279      * - relevant role caps
1280      *   - at ctx and above
1281      *   - below this ctx
1282      */
1284     if (is_null($accessdata)) {
1285         $accessdata           = array(); // named list
1286         $accessdata['ra']     = array();
1287         $accessdata['rdef']   = array();
1288         $accessdata['loaded'] = array();
1289     }
1291     $contexts = substr($context->path, 1); // kill leading slash
1292     $contexts = str_replace('/', ',', $contexts);
1294     //
1295     // Walk up and down the tree to grab all the roledefs
1296     // of interest to our role...
1297     //
1298     // NOTE: we use an IN clauses here - which
1299     // might explode on huge sites with very convoluted nesting of
1300     // categories... - extremely unlikely that the number of nested
1301     // categories is so large that we hit the limits of IN()
1302     //
1303     $sql = "SELECT ctx.path, rc.capability, rc.permission
1304               FROM {role_capabilities} rc
1305               JOIN {context} ctx
1306                    ON rc.contextid=ctx.id
1307              WHERE rc.roleid=? AND
1308                    ( ctx.id IN ($contexts) OR
1309                     ctx.path LIKE ? )
1310           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1311     $params = array($roleid, $context->path."/%");
1313     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1314         foreach ($rs as $rd) {
1315             $k = "{$rd->path}:{$roleid}";
1316             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1317         }
1318         $rs->close();
1319     }
1321     return $accessdata;
1324 /**
1325  * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global
1326  *
1327  * Used by has_capability() - but feel free
1328  * to call it if you are about to run a BIG
1329  * cron run across a bazillion users.
1330  *
1331  * @global object
1332  * @global object
1333  * @param int $userid
1334  * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid]
1335  */
1336 function load_user_accessdata($userid) {
1337     global $CFG, $ACCESSLIB_PRIVATE;
1339     $base = '/'.SYSCONTEXTID;
1341     $accessdata = get_user_access_sitewide($userid);
1342     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1343     //
1344     // provide "default role" & set 'dr'
1345     //
1346     if (!empty($CFG->defaultuserroleid)) {
1347         $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1348         if (!isset($accessdata['ra'][$base])) {
1349             $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1350         } else {
1351             array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1352         }
1353         $accessdata['dr'] = $CFG->defaultuserroleid;
1354     }
1356     //
1357     // provide "default frontpage role"
1358     //
1359     if (!empty($CFG->defaultfrontpageroleid)) {
1360         $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1361         $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1362         if (!isset($accessdata['ra'][$base])) {
1363             $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1364         } else {
1365             array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1366         }
1367     }
1368     // for dirty timestamps in cron
1369     $accessdata['time'] = time();
1371     $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1372     compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']);
1374     return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1377 /**
1378  * Use shared copy of role definitions stored in ACCESSLIB_PRIVATE->roledefinitions;
1379  *
1380  * @global object
1381  * @param array $rdefs array of role definitions in contexts
1382  */
1383 function compact_rdefs(&$rdefs) {
1384     global $ACCESSLIB_PRIVATE;
1386     /*
1387      * This is a basic sharing only, we could also
1388      * use md5 sums of values. The main purpose is to
1389      * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array.
1390      */
1392     foreach ($rdefs as $key => $value) {
1393         if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) {
1394             $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key];
1395         }
1396         $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key];
1397     }
1400 /**
1401  * A convenience function to completely load all the capabilities
1402  * for the current user.   This is what gets called from complete_user_login()
1403  * for example. Call it only _after_ you've setup $USER and called
1404  * check_enrolment_plugins();
1405  * @see check_enrolment_plugins()
1406  *
1407  * @global object
1408  * @global object
1409  * @global object
1410  */
1411 function load_all_capabilities() {
1412     global $USER, $CFG, $ACCESSLIB_PRIVATE;
1414     // roles not installed yet - we are in the middle of installation
1415     if (during_initial_install()) {
1416         return;
1417     }
1419     $base = '/'.SYSCONTEXTID;
1421     if (isguestuser()) {
1422         $guest = get_guest_role();
1424         // Load the rdefs
1425         $USER->access = get_role_access($guest->id);
1426         // Put the ghost enrolment in place...
1427         $USER->access['ra'][$base] = array($guest->id);
1430     } else if (isloggedin()) {
1432         $accessdata = get_user_access_sitewide($USER->id);
1434         //
1435         // provide "default role" & set 'dr'
1436         //
1437         if (!empty($CFG->defaultuserroleid)) {
1438             $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1439             if (!isset($accessdata['ra'][$base])) {
1440                 $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1441             } else {
1442                 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1443             }
1444             $accessdata['dr'] = $CFG->defaultuserroleid;
1445         }
1447         $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1449         //
1450         // provide "default frontpage role"
1451         //
1452         if (!empty($CFG->defaultfrontpageroleid)) {
1453             $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1454             $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1455             if (!isset($accessdata['ra'][$base])) {
1456                 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1457             } else {
1458                 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1459             }
1460         }
1461         $USER->access = $accessdata;
1463     } else if (!empty($CFG->notloggedinroleid)) {
1464         $USER->access = get_role_access($CFG->notloggedinroleid);
1465         $USER->access['ra'][$base] = array($CFG->notloggedinroleid);
1466     }
1468     // Timestamp to read dirty context timestamps later
1469     $USER->access['time'] = time();
1470     $ACCESSLIB_PRIVATE->dirtycontexts = array();
1472     // Clear to force a refresh
1473     unset($USER->mycourses);
1476 /**
1477  * A convenience function to completely reload all the capabilities
1478  * for the current user when roles have been updated in a relevant
1479  * context -- but PRESERVING switchroles and loginas.
1480  *
1481  * That is - completely transparent to the user.
1482  *
1483  * Note: rewrites $USER->access completely.
1484  *
1485  * @global object
1486  * @global object
1487  */
1488 function reload_all_capabilities() {
1489     global $USER, $DB;
1491     // error_log("reloading");
1492     // copy switchroles
1493     $sw = array();
1494     if (isset($USER->access['rsw'])) {
1495         $sw = $USER->access['rsw'];
1496         // error_log(print_r($sw,1));
1497     }
1499     unset($USER->access);
1500     unset($USER->mycourses);
1502     load_all_capabilities();
1504     foreach ($sw as $path => $roleid) {
1505         $context = $DB->get_record('context', array('path'=>$path));
1506         role_switch($roleid, $context);
1507     }
1511 /**
1512  * Adds a temp role to an accessdata array.
1513  *
1514  * Useful for the "temporary guest" access
1515  * we grant to logged-in users.
1516  *
1517  * Note - assumes a course context!
1518  *
1519  * @param object $content
1520  * @param int $roleid
1521  * @param array $accessdata
1522  * @return array Returns access data
1523  */
1524 function load_temp_role($context, $roleid, array $accessdata) {
1525     global $CFG, $DB;
1527     //
1528     // Load rdefs for the role in -
1529     // - this context
1530     // - all the parents
1531     // - and below - IOWs overrides...
1532     //
1534     // turn the path into a list of context ids
1535     $contexts = substr($context->path, 1); // kill leading slash
1536     $contexts = str_replace('/', ',', $contexts);
1538     $sql = "SELECT ctx.path, rc.capability, rc.permission
1539               FROM {context} ctx
1540               JOIN {role_capabilities} rc
1541                    ON rc.contextid=ctx.id
1542              WHERE (ctx.id IN ($contexts)
1543                     OR ctx.path LIKE ?)
1544                    AND rc.roleid = ?
1545           ORDER BY ctx.depth, ctx.path";
1546     $params = array($context->path."/%", $roleid);
1547     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1548         foreach ($rs as $rd) {
1549             $k = "{$rd->path}:{$roleid}";
1550             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1551         }
1552         $rs->close();
1553     }
1555     //
1556     // Say we loaded everything for the course context
1557     // - which we just did - if the user gets a proper
1558     // RA in this session, this data will need to be reloaded,
1559     // but that is handled by the complete accessdata reload
1560     //
1561     array_push($accessdata['loaded'], $context->path);
1563     //
1564     // Add the ghost RA
1565     //
1566     if (isset($accessdata['ra'][$context->path])) {
1567         array_push($accessdata['ra'][$context->path], $roleid);
1568     } else {
1569         $accessdata['ra'][$context->path] = array($roleid);
1570     }
1572     return $accessdata;
1575 /**
1576  * Removes any extra guest roels from accessdata
1577  * @param object $context
1578  * @param array $accessdata
1579  * @return array access data
1580  */
1581 function remove_temp_roles($context, array $accessdata) {
1582     global $DB, $USER;
1583     $sql = "SELECT DISTINCT ra.roleid AS id
1584               FROM {role_assignments} ra
1585              WHERE ra.contextid = :contextid AND ra.userid = :userid";
1586     $ras = $DB->get_records_sql($sql, array('contextid'=>$context->id, 'userid'=>$USER->id));
1588     $accessdata['ra'][$context->path] = array_keys($ras);
1589     return $accessdata;
1592 /**
1593  * Returns array of all role archetypes.
1594  *
1595  * @return array
1596  */
1597 function get_role_archetypes() {
1598     return array(
1599         'manager'        => 'manager',
1600         'coursecreator'  => 'coursecreator',
1601         'editingteacher' => 'editingteacher',
1602         'teacher'        => 'teacher',
1603         'student'        => 'student',
1604         'guest'          => 'guest',
1605         'user'           => 'user',
1606         'frontpage'      => 'frontpage'
1607     );
1610 /**
1611  * Assign the defaults found in this capability definition to roles that have
1612  * the corresponding legacy capabilities assigned to them.
1613  *
1614  * @param string $capability
1615  * @param array $legacyperms an array in the format (example):
1616  *                      'guest' => CAP_PREVENT,
1617  *                      'student' => CAP_ALLOW,
1618  *                      'teacher' => CAP_ALLOW,
1619  *                      'editingteacher' => CAP_ALLOW,
1620  *                      'coursecreator' => CAP_ALLOW,
1621  *                      'manager' => CAP_ALLOW
1622  * @return boolean success or failure.
1623  */
1624 function assign_legacy_capabilities($capability, $legacyperms) {
1626     $archetypes = get_role_archetypes();
1628     foreach ($legacyperms as $type => $perm) {
1630         $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1631         if ($type === 'admin') {
1632             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1633             $type = 'manager';
1634         }
1636         if (!array_key_exists($type, $archetypes)) {
1637             print_error('invalidlegacy', '', '', $type);
1638         }
1640         if ($roles = get_archetype_roles($type)) {
1641             foreach ($roles as $role) {
1642                 // Assign a site level capability.
1643                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1644                     return false;
1645                 }
1646             }
1647         }
1648     }
1649     return true;
1652 /**
1653  * @param object $capability a capability - a row from the capabilities table.
1654  * @return boolean whether this capability is safe - that is, whether people with the
1655  *      safeoverrides capability should be allowed to change it.
1656  */
1657 function is_safe_capability($capability) {
1658     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1661 /**********************************
1662  * Context Manipulation functions *
1663  **********************************/
1665 /**
1666  * Context creation - internal implementation.
1667  *
1668  * Create a new context record for use by all roles-related stuff
1669  * assumes that the caller has done the homework.
1670  *
1671  * DO NOT CALL THIS DIRECTLY, instead use {@link get_context_instance}!
1672  *
1673  * @param int $contextlevel
1674  * @param int $instanceid
1675  * @param int $strictness
1676  * @return object newly created context
1677  */
1678 function create_context($contextlevel, $instanceid, $strictness=IGNORE_MISSING) {
1680     global $CFG, $DB;
1682     if ($contextlevel == CONTEXT_SYSTEM) {
1683         return create_system_context();
1684     }
1686     $context = new object();
1687     $context->contextlevel = $contextlevel;
1688     $context->instanceid = $instanceid;
1690     // Define $context->path based on the parent
1691     // context. In other words... Who is your daddy?
1692     $basepath  = '/' . SYSCONTEXTID;
1693     $basedepth = 1;
1695     $result = true;
1696     $error_message = NULL;
1698     switch ($contextlevel) {
1699         case CONTEXT_COURSECAT:
1700             $sql = "SELECT ctx.path, ctx.depth
1701                       FROM {context}           ctx
1702                       JOIN {course_categories} cc
1703                            ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1704                      WHERE cc.id=?";
1705             $params = array($instanceid);
1706             if ($p = $DB->get_record_sql($sql, $params)) {
1707                 $basepath  = $p->path;
1708                 $basedepth = $p->depth;
1709             } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), '*', $strictness)) {
1710                 if (empty($category->parent)) {
1711                     // ok - this is a top category
1712                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
1713                     $basepath  = $parent->path;
1714                     $basedepth = $parent->depth;
1715                 } else {
1716                     // wrong parent category - no big deal, this can be fixed later
1717                     $basepath  = NULL;
1718                     $basedepth = 0;
1719                 }
1720             } else {
1721                 // incorrect category id
1722                 $error_message = "incorrect course category id ($instanceid)";
1723                 $result = false;
1724             }
1725             break;
1727         case CONTEXT_COURSE:
1728             $sql = "SELECT ctx.path, ctx.depth
1729                       FROM {context} ctx
1730                       JOIN {course}  c
1731                            ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
1732                      WHERE c.id=? AND c.id !=" . SITEID;
1733             $params = array($instanceid);
1734             if ($p = $DB->get_record_sql($sql, $params)) {
1735                 $basepath  = $p->path;
1736                 $basedepth = $p->depth;
1737             } else if ($course = $DB->get_record('course', array('id'=>$instanceid), '*', $strictness)) {
1738                 if ($course->id == SITEID) {
1739                     //ok - no parent category
1740                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
1741                     $basepath  = $parent->path;
1742                     $basedepth = $parent->depth;
1743                 } else {
1744                     // wrong parent category of course - no big deal, this can be fixed later
1745                     $basepath  = NULL;
1746                     $basedepth = 0;
1747                 }
1748             } else if ($instanceid == SITEID) {
1749                 // no errors for missing site course during installation
1750                 return false;
1751             } else {
1752                 // incorrect course id
1753                 $error_message = "incorrect course id ($instanceid)";
1754                 $result = false;
1755             }
1756             break;
1758         case CONTEXT_MODULE:
1759             $sql = "SELECT ctx.path, ctx.depth
1760                       FROM {context}        ctx
1761                       JOIN {course_modules} cm
1762                            ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1763                      WHERE cm.id=?";
1764             $params = array($instanceid);
1765             if ($p = $DB->get_record_sql($sql, $params)) {
1766                 $basepath  = $p->path;
1767                 $basedepth = $p->depth;
1768             } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), '*', $strictness)) {
1769                 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course, $strictness)) {
1770                     $basepath  = $parent->path;
1771                     $basedepth = $parent->depth;
1772                 } else {
1773                     // course does not exist - modules can not exist without a course
1774                     $error_message = "course does not exist ($cm->course) - modules can not exist without a course";
1775                     $result = false;
1776                 }
1777             } else {
1778                 // cm does not exist
1779                 $error_message = "cm with id $instanceid does not exist";
1780                 $result = false;
1781             }
1782             break;
1784         case CONTEXT_BLOCK:
1785             $sql = "SELECT ctx.path, ctx.depth
1786                       FROM {context} ctx
1787                       JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id)
1788                      WHERE bi.id = ?";
1789             $params = array($instanceid, CONTEXT_COURSE);
1790             if ($p = $DB->get_record_sql($sql, $params, '*', $strictness)) {
1791                 $basepath  = $p->path;
1792                 $basedepth = $p->depth;
1793             } else {
1794                 // block does not exist
1795                 $error_message = 'block or parent context does not exist';
1796                 $result = false;
1797             }
1798             break;
1799         case CONTEXT_USER:
1800             // default to basepath
1801             break;
1802     }
1804     // if grandparents unknown, maybe rebuild_context_path() will solve it later
1805     if ($basedepth != 0) {
1806         $context->depth = $basedepth+1;
1807     }
1809     if (!$result) {
1810         debugging('Error: could not insert new context level "'.
1811                   s($contextlevel).'", instance "'.
1812                   s($instanceid).'". ' . $error_message);
1814         return false;
1815     }
1817     $id = $DB->insert_record('context', $context);
1818     // can't set the full path till we know the id!
1819     if ($basedepth != 0 and !empty($basepath)) {
1820         $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
1821     }
1822     return get_context_instance_by_id($id);
1825 /**
1826  * Returns system context or NULL if can not be created yet.
1827  *
1828  * @todo can not use get_record() because we do not know if query failed :-(
1829  * switch to get_record() later
1830  *
1831  * @global object
1832  * @global object
1833  * @param bool $cache use caching
1834  * @return mixed system context or NULL
1835  */
1836 function get_system_context($cache=true) {
1837     global $DB, $ACCESSLIB_PRIVATE;
1838     if ($cache and defined('SYSCONTEXTID')) {
1839         if (is_null($ACCESSLIB_PRIVATE->systemcontext)) {
1840             $ACCESSLIB_PRIVATE->systemcontext = new object();
1841             $ACCESSLIB_PRIVATE->systemcontext->id           = SYSCONTEXTID;
1842             $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM;
1843             $ACCESSLIB_PRIVATE->systemcontext->instanceid   = 0;
1844             $ACCESSLIB_PRIVATE->systemcontext->path         = '/'.SYSCONTEXTID;
1845             $ACCESSLIB_PRIVATE->systemcontext->depth        = 1;
1846         }
1847         return $ACCESSLIB_PRIVATE->systemcontext;
1848     }
1849     try {
1850         $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM));
1851     } catch (dml_exception $e) {
1852         //table does not exist yet, sorry
1853         return NULL;
1854     }
1856     if (!$context) {
1857         $context = new object();
1858         $context->contextlevel = CONTEXT_SYSTEM;
1859         $context->instanceid   = 0;
1860         $context->depth        = 1;
1861         $context->path         = NULL; //not known before insert
1863         try {
1864             $context->id = $DB->insert_record('context', $context);
1865         } catch (dml_exception $e) {
1866             // can not create context yet, sorry
1867             return NULL;
1868         }
1869     }
1871     if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
1872         $context->instanceid   = 0;
1873         $context->path         = '/'.$context->id;
1874         $context->depth        = 1;
1875         $DB->update_record('context', $context);
1876     }
1878     if (!defined('SYSCONTEXTID')) {
1879         define('SYSCONTEXTID', $context->id);
1880     }
1882     $ACCESSLIB_PRIVATE->systemcontext = $context;
1883     return $ACCESSLIB_PRIVATE->systemcontext;
1886 /**
1887  * Remove a context record and any dependent entries,
1888  * removes context from static context cache too
1889  *
1890  * @param int $level
1891  * @param int $instanceid
1892  * @return bool returns true or throws an exception
1893  */
1894 function delete_context($contextlevel, $instanceid) {
1895     global $DB, $ACCESSLIB_PRIVATE, $CFG;
1897     // do not use get_context_instance(), because the related object might not exist,
1898     // or the context does not exist yet and it would be created now
1899     if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
1900         $DB->delete_records('role_assignments', array('contextid'=>$context->id));
1901         $DB->delete_records('role_capabilities', array('contextid'=>$context->id));
1902         $DB->delete_records('context', array('id'=>$context->id));
1903         $DB->delete_records('role_names', array('contextid'=>$context->id));
1905         // delete all files attached to this context
1906         $fs = get_file_storage();
1907         $fs->delete_area_files($context->id);
1909         // do not mark dirty contexts if parents unknown
1910         if (!is_null($context->path) and $context->depth > 0) {
1911             mark_context_dirty($context->path);
1912         }
1914         // purge static context cache if entry present
1915         unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]);
1916         unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]);
1918         blocks_delete_all_for_context($context->id);
1919         filter_delete_all_for_context($context->id);
1920     }
1922     return true;
1925 /**
1926  * Precreates all contexts including all parents
1927  *
1928  * @global object
1929  * @param int $contextlevel empty means all
1930  * @param bool $buildpaths update paths and depths
1931  * @return void
1932  */
1933 function create_contexts($contextlevel=NULL, $buildpaths=true) {
1934     global $DB;
1936     //make sure system context exists
1937     $syscontext = get_system_context(false);
1939     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
1940                              or $contextlevel == CONTEXT_COURSE
1941                              or $contextlevel == CONTEXT_MODULE
1942                              or $contextlevel == CONTEXT_BLOCK) {
1943         $sql = "INSERT INTO {context} (contextlevel, instanceid)
1944                 SELECT ".CONTEXT_COURSECAT.", cc.id
1945                   FROM {course}_categories cc
1946                  WHERE NOT EXISTS (SELECT 'x'
1947                                      FROM {context} cx
1948                                     WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
1949         $DB->execute($sql);
1951     }
1953     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
1954                              or $contextlevel == CONTEXT_MODULE
1955                              or $contextlevel == CONTEXT_BLOCK) {
1956         $sql = "INSERT INTO {context} (contextlevel, instanceid)
1957                 SELECT ".CONTEXT_COURSE.", c.id
1958                   FROM {course} c
1959                  WHERE NOT EXISTS (SELECT 'x'
1960                                      FROM {context} cx
1961                                     WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
1962         $DB->execute($sql);
1964     }
1966     if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
1967                              or $contextlevel == CONTEXT_BLOCK) {
1968         $sql = "INSERT INTO {context} (contextlevel, instanceid)
1969                 SELECT ".CONTEXT_MODULE.", cm.id
1970                   FROM {course}_modules cm
1971                  WHERE NOT EXISTS (SELECT 'x'
1972                                      FROM {context} cx
1973                                     WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
1974         $DB->execute($sql);
1975     }
1977     if (empty($contextlevel) or $contextlevel == CONTEXT_USER
1978                              or $contextlevel == CONTEXT_BLOCK) {
1979         $sql = "INSERT INTO {context} (contextlevel, instanceid)
1980                 SELECT ".CONTEXT_USER.", u.id
1981                   FROM {user} u
1982                  WHERE u.deleted=0
1983                    AND NOT EXISTS (SELECT 'x'
1984                                      FROM {context} cx
1985                                     WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
1986         $DB->execute($sql);
1988     }
1990     if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
1991         $sql = "INSERT INTO {context} (contextlevel, instanceid)
1992                 SELECT ".CONTEXT_BLOCK.", bi.id
1993                   FROM {block_instances} bi
1994                  WHERE NOT EXISTS (SELECT 'x'
1995                                      FROM {context} cx
1996                                     WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
1997         $DB->execute($sql);
1998     }
2000     if ($buildpaths) {
2001         build_context_path(false);
2002     }
2005 /**
2006  * Remove stale context records
2007  *
2008  * @global object
2009  * @return bool
2010  */
2011 function cleanup_contexts() {
2012     global $DB;
2014     $sql = "  SELECT c.contextlevel,
2015                      c.instanceid AS instanceid
2016                 FROM {context} c
2017                 LEFT OUTER JOIN {course}_categories t
2018                      ON c.instanceid = t.id
2019                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
2020             UNION
2021               SELECT c.contextlevel,
2022                      c.instanceid
2023                 FROM {context} c
2024                 LEFT OUTER JOIN {course} t
2025                      ON c.instanceid = t.id
2026                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
2027             UNION
2028               SELECT c.contextlevel,
2029                      c.instanceid
2030                 FROM {context} c
2031                 LEFT OUTER JOIN {course}_modules t
2032                      ON c.instanceid = t.id
2033                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
2034             UNION
2035               SELECT c.contextlevel,
2036                      c.instanceid
2037                 FROM {context} c
2038                 LEFT OUTER JOIN {user} t
2039                      ON c.instanceid = t.id
2040                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
2041             UNION
2042               SELECT c.contextlevel,
2043                      c.instanceid
2044                 FROM {context} c
2045                 LEFT OUTER JOIN {block_instances} t
2046                      ON c.instanceid = t.id
2047                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
2048            ";
2050     // transactions used only for performance reasons here
2051     $transaction = $DB->start_delegated_transaction();
2053     if ($rs = $DB->get_recordset_sql($sql)) {
2054         foreach ($rs as $ctx) {
2055             delete_context($ctx->contextlevel, $ctx->instanceid);
2056         }
2057         $rs->close();
2058     }
2060     $transaction->allow_commit();
2061     return true;
2064 /**
2065  * Preloads all contexts relating to a course: course, modules. Block contexts
2066  * are no longer loaded here. The contexts for all the blocks on the current
2067  * page are now efficiently loaded by {@link block_manager::load_blocks()}.
2068  *
2069  * @param int $courseid Course ID
2070  * @return void
2071  */
2072 function preload_course_contexts($courseid) {
2073     global $DB, $ACCESSLIB_PRIVATE;
2075     // Users can call this multiple times without doing any harm
2076     global $ACCESSLIB_PRIVATE;
2077     if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
2078         return;
2079     }
2081     $params = array($courseid, $courseid, $courseid);
2082     $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2083               FROM {course_modules} cm
2084               JOIN {context} x ON x.instanceid=cm.id
2085              WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2087          UNION ALL
2089             SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2090               FROM {context} x
2091              WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2093     $rs = $DB->get_recordset_sql($sql, $params);
2094     foreach($rs as $context) {
2095         cache_context($context);
2096     }
2097     $rs->close();
2098     $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
2101 /**
2102  * Get the context instance as an object. This function will create the
2103  * context instance if it does not exist yet.
2104  *
2105  * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2106  *
2107  * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2108  * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2109  *      for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
2110  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2111  *      MUST_EXIST means throw exception if no record or multiple records found
2112  * @return object The context object.
2113  */
2114 function get_context_instance($contextlevel, $instance=0, $strictness=IGNORE_MISSING) {
2116     global $DB, $ACCESSLIB_PRIVATE;
2117     static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
2119 /// System context has special cache
2120     if ($contextlevel == CONTEXT_SYSTEM) {
2121         return get_system_context();
2122     }
2124 /// check allowed context levels
2125     if (!in_array($contextlevel, $allowed_contexts)) {
2126         // fatal error, code must be fixed - probably typo or switched parameters
2127         print_error('invalidcourselevel');
2128     }
2130     if (!is_array($instance)) {
2131     /// Check the cache
2132         if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) {  // Already cached
2133             return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
2134         }
2136     /// Get it from the database, or create it
2137         if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
2138             $context = create_context($contextlevel, $instance, $strictness);
2139         }
2141     /// Only add to cache if context isn't empty.
2142         if (!empty($context)) {
2143             cache_context($context);
2144         }
2146         return $context;
2147     }
2150 /// ok, somebody wants to load several contexts to save some db queries ;-)
2151     $instances = $instance;
2152     $result = array();
2154     foreach ($instances as $key=>$instance) {
2155     /// Check the cache first
2156         if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) {  // Already cached
2157             $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
2158             unset($instances[$key]);
2159             continue;
2160         }
2161     }
2163     if ($instances) {
2164         list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2165         array_unshift($params, $contextlevel);
2166         $sql = "SELECT instanceid, id, contextlevel, path, depth
2167                   FROM {context}
2168                  WHERE contextlevel=? AND instanceid $instanceids";
2170         if (!$contexts = $DB->get_records_sql($sql, $params)) {
2171             $contexts = array();
2172         }
2174         foreach ($instances as $instance) {
2175             if (isset($contexts[$instance])) {
2176                 $context = $contexts[$instance];
2177             } else {
2178                 $context = create_context($contextlevel, $instance);
2179             }
2181             if (!empty($context)) {
2182                 cache_context($context);
2183             }
2185             $result[$instance] = $context;
2186         }
2187     }
2189     return $result;
2193 /**
2194  * Get a context instance as an object, from a given context id.
2195  *
2196  * @param mixed $id a context id or array of ids.
2197  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2198  *                        MUST_EXIST means throw exception if no record or multiple records found
2199  * @return mixed object, array of the context object, or false.
2200  */
2201 function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) {
2202     global $DB, $ACCESSLIB_PRIVATE;
2204     if ($id == SYSCONTEXTID) {
2205         return get_system_context();
2206     }
2208     if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) {  // Already cached
2209         return $ACCESSLIB_PRIVATE->contextsbyid[$id];
2210     }
2212     if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
2213         cache_context($context);
2214         return $context;
2215     }
2217     return false;
2221 /**
2222  * Get the local override (if any) for a given capability in a role in a context
2223  *
2224  * @global object
2225  * @param int $roleid
2226  * @param int $contextid
2227  * @param string $capability
2228  */
2229 function get_local_override($roleid, $contextid, $capability) {
2230     global $DB;
2231     return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
2234 /**
2235  * Returns context instance plus related course and cm instances
2236  * @param int $contextid
2237  * @return array of ($context, $course, $cm)
2238  */
2239 function get_context_info_array($contextid) {
2240     global $DB;
2242     $context = get_context_instance_by_id($contextid, MUST_EXIST);
2243     $course  = NULL;
2244     $cm      = NULL;
2246     if ($context->contextlevel == CONTEXT_COURSE) {
2247         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2249     } else if ($context->contextlevel == CONTEXT_MODULE) {
2250         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2251         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2253     } else if ($context->contextlevel == CONTEXT_BLOCK) {
2254         $parentcontexts = get_parent_contexts($context, false);
2255         $parent = reset($parentcontexts);
2256         $parent = get_context_instance_by_id($parent);
2258         if ($parent->contextlevel == CONTEXT_COURSE) {
2259             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2260         } else if ($parent->contextlevel == CONTEXT_MODULE) {
2261             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2262             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2263         }
2264     }
2266     return array($context, $course, $cm);
2270 //////////////////////////////////////
2271 //    DB TABLE RELATED FUNCTIONS    //
2272 //////////////////////////////////////
2274 /**
2275  * function that creates a role
2276  *
2277  * @global object
2278  * @param string $name role name
2279  * @param string $shortname role short name
2280  * @param string $description role description
2281  * @param string $archetype
2282  * @return mixed id or dml_exception
2283  */
2284 function create_role($name, $shortname, $description, $archetype='') {
2285     global $DB;
2287     if (strpos($archetype, 'moodle/legacy:') !== false) {
2288         throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
2289     }
2291     // verify role archetype actually exists
2292     $archetypes = get_role_archetypes();
2293     if (empty($archetypes[$archetype])) {
2294         $archetype = '';
2295     }
2297     // Get the system context.
2298     $context = get_context_instance(CONTEXT_SYSTEM);
2300     // Insert the role record.
2301     $role = new object();
2302     $role->name        = $name;
2303     $role->shortname   = $shortname;
2304     $role->description = $description;
2305     $role->archetype   = $archetype;
2307     //find free sortorder number
2308     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
2309     if (empty($role->sortorder)) {
2310         $role->sortorder = 1;
2311     }
2312     $id = $DB->insert_record('role', $role);
2314     return $id;
2317 /**
2318  * Function that deletes a role and cleanups up after it
2319  *
2320  * @param int $roleid id of role to delete
2321  * @return bool always true
2322  */
2323 function delete_role($roleid) {
2324     global $CFG, $DB;
2326     // first unssign all users
2327     role_unassign_all(array('roleid'=>$roleid));
2329     // cleanup all references to this role, ignore errors
2330     $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
2331     $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
2332     $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
2333     $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2334     $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2335     $DB->delete_records('role_names',          array('roleid'=>$roleid));
2336     $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
2338     // finally delete the role itself
2339     // get this before the name is gone for logging
2340     $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
2342     $DB->delete_records('role', array('id'=>$roleid));
2344     add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
2346     return true;
2349 /**
2350  * Function to write context specific overrides, or default capabilities.
2351  *
2352  * @global object
2353  * @global object
2354  * @param string module string name
2355  * @param string capability string name
2356  * @param int contextid context id
2357  * @param int roleid role id
2358  * @param int permission int 1,-1 or -1000 should not be writing if permission is 0
2359  * @return bool
2360  */
2361 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
2362     global $USER, $DB;
2364     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
2365         unassign_capability($capability, $roleid, $contextid);
2366         return true;
2367     }
2369     $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
2371     if ($existing and !$overwrite) {   // We want to keep whatever is there already
2372         return true;
2373     }
2375     $cap = new object;
2376     $cap->contextid = $contextid;
2377     $cap->roleid = $roleid;
2378     $cap->capability = $capability;
2379     $cap->permission = $permission;
2380     $cap->timemodified = time();
2381     $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
2383     if ($existing) {
2384         $cap->id = $existing->id;
2385         $DB->update_record('role_capabilities', $cap);
2386     } else {
2387         $c = $DB->get_record('context', array('id'=>$contextid));
2388         $DB->insert_record('role_capabilities', $cap);
2389     }
2390     return true;
2393 /**
2394  * Unassign a capability from a role.
2395  *
2396  * @global object
2397  * @param int $roleid the role id
2398  * @param string $capability the name of the capability
2399  * @return boolean success or failure
2400  */
2401 function unassign_capability($capability, $roleid, $contextid=NULL) {
2402     global $DB;
2404     if (!empty($contextid)) {
2405         // delete from context rel, if this is the last override in this context
2406         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid));
2407     } else {
2408         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
2409     }
2410     return true;
2414 /**
2415  * Get the roles that have a given capability assigned to it
2416  * Get the roles that have a given capability assigned to it. This function
2417  * does not resolve the actual permission of the capability. It just checks
2418  * for assignment only.
2419  *
2420  * @global object
2421  * @global object
2422  * @param string $capability - capability name (string)
2423  * @param string $permission - optional, the permission defined for this capability
2424  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL
2425  * @param object $contect
2426  * @return mixed array or role objects
2427  */
2428 function get_roles_with_capability($capability, $permission=NULL, $context=NULL) {
2429     global $CFG, $DB;
2431     $params = array();
2433     if ($context) {
2434         if ($contexts = get_parent_contexts($context)) {
2435             $listofcontexts = '('.implode(',', $contexts).')';
2436         } else {
2437             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2438             $listofcontexts = '('.$sitecontext->id.')'; // must be site
2439         }
2440         $contextstr = "AND (rc.contextid = ? OR  rc.contextid IN $listofcontexts)";
2441         $params[] = $context->id;
2442     } else {
2443         $contextstr = '';
2444     }
2446     $selectroles = "SELECT r.*
2447                       FROM {role} r,
2448                            {role_capabilities} rc
2449                      WHERE rc.capability = ?
2450                            AND rc.roleid = r.id $contextstr";
2452     array_unshift($params, $capability);
2454     if (isset($permission)) {
2455         $selectroles .= " AND rc.permission = ?";
2456         $params[] = $permission;
2457     }
2458     return $DB->get_records_sql($selectroles, $params);
2462 /**
2463  * This function makes a role-assignment (a role for a user in a particular context)
2464  *
2465  * @param int $roleid the role of the id
2466  * @param int $userid userid
2467  * @param int $contextid id of the context
2468  * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
2469  * @prama int $itemid id of enrolment/auth plugin
2470  * @param string $timemodified defaults to current time
2471  * @return int new/existing id of the assignment
2472  */
2473 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
2474     global $USER, $CFG, $DB;
2476     // first of all detect if somebody is using old style parameters
2477     if ($contextid === 0 or is_numeric($component)) {
2478         throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
2479     }
2481     // now validate all parameters
2482     if (empty($roleid)) {
2483         throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
2484     }
2486     if (empty($userid)) {
2487         throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
2488     }
2490     if ($itemid) {
2491         if (strpos($component, '_') === false) {
2492             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
2493         }
2494     } else {
2495         $itemid = 0;
2496         if ($component !== '' and strpos($component, '_') === false) {
2497             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2498         }
2499     }
2501     if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
2502         throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
2503         return false;
2504     }
2506     $context = get_context_instance_by_id($contextid, MUST_EXIST);
2508     if (!$timemodified) {
2509         $timemodified = time();
2510     }
2512 /// Check for existing entry
2513     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
2515     if ($ras) {
2516         // role already assigned - this should not happen
2517         if (count($ras) > 1) {
2518             //very weird - remove all duplicates!
2519             $ra = array_shift($ras);
2520             foreach ($ras as $r) {
2521                 $DB->delete_records('role_assignments', array('id'=>$r->id));
2522             }
2523         } else {
2524             $ra = reset($ras);
2525         }
2527         // actually there is no need to update, reset anything or trigger any event, so just return
2528         return $ra->id;
2529     }
2531     // Create a new entry
2532     $ra = new object();
2533     $ra->roleid       = $roleid;
2534     $ra->contextid    = $context->id;
2535     $ra->userid       = $userid;
2536     $ra->component    = $component;
2537     $ra->itemid       = $itemid;
2538     $ra->timemodified = $timemodified;
2539     $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
2541     $ra->id = $DB->insert_record('role_assignments', $ra);
2543     // mark context as dirty - again expensive, but needed
2544     mark_context_dirty($context->path);
2546     if (!empty($USER->id) && $USER->id == $userid) {
2547         // If the user is the current user, then do full reload of capabilities too.
2548         load_all_capabilities();
2549     }
2551     events_trigger('role_assigned', $ra);
2553     return $ra->id;
2556 /**
2557  * Removes one role assignment
2558  *
2559  * @param int $roleid
2560  * @param int  $userid
2561  * @param int  $contextid
2562  * @param string $component
2563  * @param int  $itemid
2564  * @return void
2565  */
2566 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
2567     global $USER, $CFG, $DB;
2569     // first make sure the params make sense
2570     if ($roleid == 0 or $userid == 0 or $contextid == 0) {
2571         throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
2572     }
2574     if ($itemid) {
2575         if (strpos($component, '_') === false) {
2576             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
2577         }
2578     } else {
2579         $itemid = 0;
2580         if ($component !== '' and strpos($component, '_') === false) {
2581             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
2582         }
2583     }
2585     role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
2588 /**
2589  * Removes multiple role assignments, parameters may contain:
2590  *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
2591  *
2592  * @param array $params role assignment parameters
2593  * @param bool $subcontexts unassign in subcontexts too
2594  * @param bool $includmanual include manual role assignments too
2595  * @return void
2596  */
2597 function role_unassign_all(array $params, $subcontexts = false, $includemanual=false) {
2598     global $USER, $CFG, $DB;
2600     if (!$params) {
2601         throw new coding_exception('Missing parameters in role_unsassign_all() call');
2602     }
2604     $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
2605     foreach ($params as $key=>$value) {
2606         if (!in_array($key, $allowed)) {
2607             throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
2608         }
2609     }
2611     if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
2612         throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
2613     }
2615     if ($includemanual) {
2616         if (!isset($params['component']) or $params['component'] === '') {
2617             throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
2618         }
2619     }
2621     if ($subcontexts) {
2622         if (empty($params['contextid'])) {
2623             throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
2624         }
2625     }
2627     $ras = $DB->get_records('role_assignments', $params);
2628     foreach($ras as $ra) {
2629         $DB->delete_records('role_assignments', array('id'=>$ra->id));
2630         if ($context = get_context_instance_by_id($ra->contextid)) {
2631             // this is a bit expensive but necessary
2632             mark_context_dirty($context->path);
2633             /// If the user is the current user, then do full reload of capabilities too.
2634             if (!empty($USER->id) && $USER->id == $ra->userid) {
2635                 load_all_capabilities();
2636             }
2637         }
2638         events_trigger('role_unassigned', $ra);
2639     }
2640     unset($ras);
2642     // process subcontexts
2643     if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) {
2644         $contexts = get_child_contexts($context);
2645         $mparams = $params;
2646         foreach($contexts as $context) {
2647             $mparams['contextid'] = $context->id;
2648             $ras = $DB->get_records('role_assignments', $mparams);
2649             foreach($ras as $ra) {
2650                 $DB->delete_records('role_assignments', array('id'=>$ra->id));
2651                 // this is a bit expensive but necessary
2652                 mark_context_dirty($context->path);
2653                 /// If the user is the current user, then do full reload of capabilities too.
2654                 if (!empty($USER->id) && $USER->id == $ra->userid) {
2655                     load_all_capabilities();
2656                 }
2657                 events_trigger('role_unassigned', $ra);
2658             }
2659         }
2660     }
2662     // do this once more for all manual role assignments
2663     if ($includemanual) {
2664         $params['component'] = '';
2665         role_unassign_all($params, $subcontexts, false);
2666     }
2670 /**
2671  * Determines if a user is currently logged in
2672  *
2673  * @return bool
2674  */
2675 function isloggedin() {
2676     global $USER;
2678     return (!empty($USER->id));
2681 /**
2682  * Determines if a user is logged in as real guest user with username 'guest'.
2683  *
2684  * @param int|object $user mixed user object or id, $USER if not specified
2685  * @return bool true if user is the real guest user, false if not logged in or other user
2686  */
2687 function isguestuser($user = NULL) {
2688     global $USER, $DB, $CFG;
2690     // make sure we have the user id cached in config table, because we are going to use it a lot
2691     if (empty($CFG->siteguest)) {
2692         if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
2693             // guest does not exist yet, weird
2694             return false;
2695         }
2696         set_config('siteguest', $guestid);
2697     }
2698     if ($user === NULL) {
2699         $user = $USER;
2700     }
2702     if ($user === NULL) {
2703         // happens when setting the $USER
2704         return false;
2706     } else if (is_numeric($user)) {
2707         return ($CFG->siteguest == $user);
2709     } else if (is_object($user)) {
2710         if (empty($user->id)) {
2711             return false; // not logged in means is not be guest
2712         } else {
2713             return ($CFG->siteguest == $user->id);
2714         }
2716     } else {
2717         throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
2718     }
2721 /**
2722  * Does user have a (temporary or real) guest access to course?
2723  *
2724  * @param object $context
2725  * @param object|int $user
2726  * @return bool
2727  */
2728 function is_guest($context, $user = NULL) {
2729     // first find the course context
2730     $coursecontext = get_course_context($context);
2732     // make sure there is a real user specified
2733     if ($user === NULL) {
2734         $userid = !empty($USER->id) ? $USER->id : 0;
2735     } else {
2736         $userid = !empty($user->id) ? $user->id : $user;
2737     }
2739     if (isguestuser($userid)) {
2740         // can not inspect or be enrolled
2741         return true;
2742     }
2744     if (has_capability('moodle/course:view', $coursecontext, $user)) {
2745         // viewing users appear out of nowhere, they are neither guests nor participants
2746         return false;
2747     }
2749     // consider only real active enrolments here
2750     if (is_enrolled($coursecontext, $user, '', true)) {
2751         return false;
2752     }
2754     return true;
2758 /**
2759  * Returns true if the user has moodle/course:view capability in the course,
2760  * this is intended for admins, managers (aka small admins), inspectors, etc.
2761  *
2762  * @param object $context
2763  * @param int|object $user, if NULL $USER is used
2764  * @param string $withcapability extra capability name
2765  * @return bool
2766  */
2767 function is_viewing($context, $user = NULL, $withcapability = '') {
2768     global $USER;
2770     // first find the course context
2771     $coursecontext = get_course_context($context);
2773     if (isguestuser($user)) {
2774         // can not inspect
2775         return false;
2776     }
2778     if (!has_capability('moodle/course:view', $coursecontext, $user)) {
2779         // admins are allowed to inspect courses
2780         return false;
2781     }
2783     if ($withcapability and !has_capability($withcapability, $context, $user)) {
2784         // site admins always have the capability, but the enrolment above blocks
2785         return false;
2786     }
2788     return true;
2791 /**
2792  * Returns true if user is enrolled (is participating) in course
2793  * this is intended for students and teachers.
2794  *
2795  * @param object $context
2796  * @param int|object $user, if NULL $USER is used, otherwise user object or id expected
2797  * @param string $withcapability extra capability name
2798  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2799  * @return bool
2800  */
2801 function is_enrolled($context, $user = NULL, $withcapability = '', $onlyactive = false) {
2802     global $USER, $DB;
2804     // first find the course context
2805     $coursecontext = get_course_context($context);
2807     // make sure there is a real user specified
2808     if ($user === NULL) {
2809         $userid = !empty($USER->id) ? $USER->id : 0;
2810     } else {
2811         $userid = !empty($user->id) ? $user->id : $user;
2812     }
2814     if (empty($userid)) {
2815         // not-logged-in!
2816         return false;
2817     } else if (isguestuser($userid)) {
2818         // guest account can not be enrolled anywhere
2819         return false;
2820     }
2822     if ($coursecontext->instanceid == SITEID) {
2823         // everybody participates on frontpage
2824     } else {
2825         if ($onlyactive) {
2826             $sql = "SELECT ue.*
2827                       FROM {user_enrolments} ue
2828                       JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2829                       JOIN {user} u ON u.id = ue.userid
2830                      WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
2831             $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2832             // this result should be very small, better not do the complex time checks in sql for now ;-)
2833             $enrolments = $DB->get_records_sql($sql, $params);
2834             $now = time();
2835             // make sure the enrol period is ok
2836             $result = false;
2837             foreach ($enrolments as $e) {
2838                 if ($e->timestart > $now) {
2839                     continue;
2840                 }
2841                 if ($e->timeend and $e->timeend < $now) {
2842                     continue;
2843                 }
2844                 $result = true;
2845                 break;
2846             }
2847             if (!$result) {
2848                 return false;
2849             }
2851         } else {
2852             // any enrolment is good for us here, even outdated, disabled or inactive
2853             $sql = "SELECT 'x'
2854                       FROM {user_enrolments} ue
2855                       JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2856                       JOIN {user} u ON u.id = ue.userid
2857                      WHERE ue.userid = :userid AND u.deleted = 0";
2858             $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2859             if (!$DB->record_exists_sql($sql, $params)) {
2860                 return false;
2861             }
2862         }
2863     }
2865     if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2866         return false;
2867     }
2869     return true;
2872 /**
2873  * Returns array with sql code and parameters returning all ids
2874  * of users enrolled into course.
2875  *
2876  * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2877  *
2878  * @param object $context
2879  * @param string $withcapability
2880  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2881  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2882  * @return array list($sql, $params)
2883  */
2884 function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2885     global $DB;
2887     // use unique prefix just in case somebody makes some SQL magic with the result
2888     static $i = 0;
2889     $i++;
2890     $prefix = 'eu'.$i.'_';
2892     // first find the course context
2893     $coursecontext = get_course_context($context);
2895     $isfrontpage = ($coursecontext->instanceid == SITEID);
2897     $joins  = array();
2898     $wheres = array();
2899     $params = array();
2901     list($contextids, $contextpaths) = get_context_info_list($context);
2903     // get all relevant capability info for all roles
2904     if ($withcapability) {
2905         list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx00');
2906         $cparams['cap'] = $withcapability;
2908         $defs = array();
2909         $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
2910                   FROM {role_capabilities} rc
2911                   JOIN {context} ctx on rc.contextid = ctx.id
2912                  WHERE rc.contextid $incontexts AND rc.capability = :cap";
2913         $rcs = $DB->get_records_sql($sql, $cparams);
2914         foreach ($rcs as $rc) {
2915             $defs[$rc->path][$rc->roleid] = $rc->permission;
2916         }
2918         $access = array();
2919         if (!empty($defs)) {
2920             foreach ($contextpaths as $path) {
2921                 if (empty($defs[$path])) {
2922                     continue;
2923                 }
2924                 foreach($defs[$path] as $roleid => $perm) {
2925                     if ($perm == CAP_PROHIBIT) {
2926                         $access[$roleid] = CAP_PROHIBIT;
2927                         continue;
2928                     }
2929                     if (!isset($access[$roleid])) {
2930                         $access[$roleid] = (int)$perm;
2931                     }
2932                 }
2933             }
2934         }
2936         unset($defs);
2938         // make lists of roles that are needed and prohibited
2939         $needed     = array(); // one of these is enough
2940         $prohibited = array(); // must not have any of these
2941         if ($withcapability) {
2942             foreach ($access as $roleid => $perm) {
2943                 if ($perm == CAP_PROHIBIT) {
2944                     unset($needed[$roleid]);
2945                     $prohibited[$roleid] = true;
2946                 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
2947                     $needed[$roleid] = true;
2948                 }
2949             }
2950         }
2952         $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : NULL;
2953         $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : NULL;
2955         $nobody = false;
2957         if ($isfrontpage) {
2958             if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
2959                 $nobody = true;
2960             } else if (!empty($neded[$defaultuserroleid]) or !empty($neded[$defaultfrontpageroleid])) {
2961                 // everybody not having prohibit has the capability
2962                 $needed = array();
2963             } else if (empty($needed)) {
2964                 $nobody = true;
2965             }
2966         } else {
2967             if (!empty($prohibited[$defaultuserroleid])) {
2968                 $nobody = true;
2969             } else if (!empty($neded[$defaultuserroleid])) {
2970                 // everybody not having prohibit has the capability
2971                 $needed = array();
2972             } else if (empty($needed)) {
2973                 $nobody = true;
2974             }
2975         }
2977         if ($nobody) {
2978             // nobody can match so return some SQL that does not return any results
2979             $wheres[] = "1 = 2";
2981         } else {
2983             if ($needed) {
2984                 $ctxids = implode(',', $contextids);
2985                 $roleids = implode(',', array_keys($needed));
2986                 $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))";
2987             }
2989             if ($prohibited) {
2990                 $ctxids = implode(',', $contextids);
2991                 $roleids = implode(',', array_keys($prohibited));
2992                 $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))";
2993                 $wheres[] = "{$prefix}ra4 IS NULL";
2994             }
2996             if ($groupid) {
2997                 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.id = :{$prefix}gmid)";
2998                 $params["{$prefix}gmid"] = $groupid;
2999             }
3000         }
3002     }
3004     $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.username <> 'guest'";
3006     if ($isfrontpage) {
3007         // all users are "enrolled" on the frontpage
3008     } else {
3009         $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
3010         $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
3011         $params[$prefix.'courseid'] = $coursecontext->instanceid;
3013         if ($onlyactive) {
3014             $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
3015             $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
3016             $now = round(time(), -2); // rounding helps caching in DB
3017             $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
3018                                                  $prefix.'active'=>ENROL_USER_ACTIVE,
3019                                                  $prefix.'now1'=>$now, $prefix.'now2'=>$now));
3020         }
3021     }
3023     $joins = implode("\n", $joins);
3024     $wheres = "WHERE ".implode(" AND ", $wheres);
3026     $sql = "SELECT DISTINCT {$prefix}u.id
3027                FROM {user} {$prefix}u
3028              $joins
3029             $wheres";
3031     return array($sql, $params);
3034 /**
3035  * Returns list of users enrolled into course.
3036  * @param object $context
3037  * @param string $withcapability
3038  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3039  * @param string $userfields requested user record fields
3040  * @param string $orderby
3041  * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
3042  * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
3043  * @return array of user records
3044  */
3045 function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) {
3046     global $DB;
3048     list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3049     $sql = "SELECT $userfields
3050               FROM {user} u
3051               JOIN ($esql) je ON je.id = u.id
3052              WHERE u.deleted = 0";
3054     if ($orderby) {
3055         $sql = "$sql ORDER BY $orderby";
3056     } else {
3057         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
3058     }
3060     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3063 /**
3064  * Counts list of users enrolled into course (as per above function)
3065  * @param object $context
3066  * @param string $withcapability
3067  * @param int $groupid 0 means ignore groups, any other value limits the result by group id
3068  * @return array of user records
3069  */
3070 function count_enrolled_users($context, $withcapability = '', $groupid = 0) {
3071     global $DB;
3073     list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid);
3074     $sql = "SELECT count(u.id)
3075               FROM {user} u
3076               JOIN ($esql) je ON je.id = u.id
3077              WHERE u.deleted = 0";
3079     return $DB->count_records_sql($sql, $params);
3083 /**
3084  * Loads the capability definitions for the component (from file).
3085  *
3086  * Loads the capability definitions for the component (from file). If no
3087  * capabilities are defined for the component, we simply return an empty array.
3088  *
3089  * @global object
3090  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
3091  * @return array array of capabilities
3092  */
3093 function load_capability_def($component) {
3094     $defpath = get_component_directory($component).'/db/access.php';
3096     $capabilities = array();
3097     if (file_exists($defpath)) {
3098         require($defpath);
3099         if (!empty(${$component.'_capabilities'})) {
3100             // BC capability array name
3101             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
3102             debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files');
3103             $capabilities = ${$component.'_capabilities'};
3104         }
3105     }
3107     return $capabilities;
3111 /**
3112  * Gets the capabilities that have been cached in the database for this component.
3113  * @param string $component - examples: 'moodle', 'mod_forum'
3114  * @return array array of capabilities
3115  */
3116 function get_cached_capabilities($component='moodle') {
3117     global $DB;
3118     return $DB->get_records('capabilities', array('component'=>$component));
3121 /**
3122  * Returns default capabilities for given role archetype.
3123  * @param string $archetype role archetype
3124  * @return array
3125  */
3126 function get_default_capabilities($archetype) {
3127     global $DB;
3129     if (!$archetype) {
3130         return array();
3131     }
3133     $alldefs = array();
3134     $defaults = array();
3135     $components = array();
3136     $allcaps = $DB->get_records('capabilities');
3138     foreach ($allcaps as $cap) {
3139         if (!in_array($cap->component, $components)) {
3140             $components[] = $cap->component;
3141             $alldefs = array_merge($alldefs, load_capability_def($cap->component));
3142         }
3143     }
3144     foreach($alldefs as $name=>$def) {
3145         // Use array 'archetypes if available. Only if not specified, use 'legacy'.
3146         if (isset($def['archetypes'])) {
3147             if (isset($def['archetypes'][$archetype])) {
3148                 $defaults[$name] = $def['archetypes'][$archetype];
3149             }
3150         // 'legacy' is for backward compatibility with 1.9 access.php
3151         } else {
3152             if (isset($def['legacy'][$archetype])) {
3153                 $defaults[$name] = $def['legacy'][$archetype];
3154             }
3155         }
3156     }
3158     return $defaults;
3161 /**
3162  * Reset role capabilities to default according to selected role archetype.
3163  * If no archetype selected, removes all capabilities.
3164  * @param int $roleid
3165  */
3166 function reset_role_capabilities($roleid) {
3167     global $DB;
3169     $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
3170     $defaultcaps = get_default_capabilities($role->archetype);
3172     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
3174     $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
3176     foreach($defaultcaps as $cap=>$permission) {
3177         assign_capability($cap, $permission, $roleid, $sitecontext->id);
3178     }
3181 /**
3182  * Updates the capabilities table with the component capability definitions.
3183  * If no parameters are given, the function updates the core moodle
3184  * capabilities.
3185  *
3186  * Note that the absence of the db/access.php capabilities definition file
3187  * will cause any stored capabilities for the component to be removed from
3188  * the database.
3189  *
3190  * @global object
3191  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
3192  * @return boolean true if success, exception in case of any problems
3193  */
3194 function update_capabilities($component='moodle') {
3195     global $DB, $OUTPUT;
3197     $storedcaps = array();
3199     $filecaps = load_capability_def($component);
3200     $cachedcaps = get_cached_capabilities($component);
3201     if ($cachedcaps) {
3202         foreach ($cachedcaps as $cachedcap) {
3203             array_push($storedcaps, $cachedcap->name);
3204             // update risk bitmasks and context levels in existing capabilities if needed
3205             if (array_key_exists($cachedcap->name, $filecaps)) {
3206                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
3207                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
3208                 }
3209                 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
3210                     $updatecap = new object();
3211                     $updatecap->id = $cachedcap->id;
3212                     $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
3213                     $DB->update_record('capabilities', $updatecap);
3214                 }
3215                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
3216                     $updatecap = new object();
3217                     $updatecap->id = $cachedcap->id;
3218                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
3219                     $DB->update_record('capabilities', $updatecap);
3220                 }
3222                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
3223                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
3224                 }
3225                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
3226                     $updatecap = new object();
3227                     $updatecap->id = $cachedcap->id;
3228                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
3229                     $DB->update_record('capabilities', $updatecap);
3230                 }
3231             }
3232         }
3233     }
3235     // Are there new capabilities in the file definition?
3236     $newcaps = array();
3238     foreach ($filecaps as $filecap => $def) {
3239         if (!$storedcaps ||
3240                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3241             if (!array_key_exists('riskbitmask', $def)) {
3242                 $def['riskbitmask'] = 0; // no risk if not specified
3243             }
3244             $newcaps[$filecap] = $def;
3245         }
3246     }
3247     // Add new capabilities to the stored definition.
3248     foreach ($newcaps as $capname => $capdef) {
3249         $capability = new object();
3250         $capability->name = $capname;
3251         $capability->captype = $capdef['captype'];
3252         $capability->contextlevel = $capdef['contextlevel'];
3253         $capability->component = $component;
3254         $capability->riskbitmask = $capdef['riskbitmask'];
3256         $DB->insert_record('capabilities', $capability, false);
3258         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3259             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
3260                 foreach ($rolecapabilities as $rolecapability){
3261                     //assign_capability will update rather than insert if capability exists
3262                     if (!assign_capability($capname, $rolecapability->permission,
3263                                             $rolecapability->roleid, $rolecapability->contextid, true)){
3264                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
3265                     }
3266                 }
3267             }
3268         // we ignore archetype key if we have cloned permissions
3269         } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
3270             assign_legacy_capabilities($capname, $capdef['archetypes']);
3271         // 'legacy' is for backward compatibility with 1.9 access.php
3272         } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
3273             assign_legacy_capabilities($capname, $capdef['legacy']);
3274         }
3275     }
3276     // Are there any capabilities that have been removed from the file
3277     // definition that we need to delete from the stored capabilities and
3278     // role assignments?
3279     capabilities_cleanup($component, $filecaps);
3281     // reset static caches
3282     $ACCESSLIB_PRIVATE->capabilities = NULL;
3284     return true;
3288 /**
3289  * Deletes cached capabilities that are no longer needed by the component.
3290  * Also unassigns these capabilities from any roles that have them.
3291  *
3292  * @global object
3293  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
3294  * @param array $newcapdef array of the new capability definitions that will be
3295  *                     compared with the cached capabilities
3296  * @return int number of deprecated capabilities that have been removed
3297  */
3298 function capabilities_cleanup($component, $newcapdef=NULL) {
3299     global $DB;
3301     $removedcount = 0;
3303     if ($cachedcaps = get_cached_capabilities($component)) {
3304         foreach ($cachedcaps as $cachedcap) {
3305             if (empty($newcapdef) ||
3306                         array_key_exists($cachedcap->name, $newcapdef) === false) {
3308                 // Remove from capabilities cache.
3309                 $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
3310                 $removedcount++;
3311                 // Delete from roles.
3312                 if ($roles = get_roles_with_capability($cachedcap->name)) {
3313                     foreach($roles as $role) {
3314                         if (!unassign_capability($cachedcap->name, $role->id)) {
3315                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
3316                         }
3317                     }
3318                 }
3319             } // End if.
3320         }
3321     }
3322     return $removedcount;
3327 //////////////////
3328 // UI FUNCTIONS //
3329 //////////////////
3331 /**
3332  * @param integer $contextlevel $context->context level. One of the CONTEXT_... constants.
3333  * @return string the name for this type of context.
3334  */
3335 function get_contextlevel_name($contextlevel) {
3336     static $strcontextlevels = NULL;
3337     if (is_null($strcontextlevels)) {
3338         $strcontextlevels = array(
3339             CONTEXT_SYSTEM => get_string('coresystem'),
3340             CONTEXT_USER => get_string('user'),
3341             CONTEXT_COURSECAT => get_string('category'),
3342             CONTEXT_COURSE => get_string('course'),
3343             CONTEXT_MODULE => get_string('activitymodule'),
3344             CONTEXT_BLOCK => get_string('block')
3345         );
3346     }
3347     return $strcontextlevels[$contextlevel];
3350 /**
3351  * Prints human readable context identifier.
3352  *
3353  * @global object
3354  * @param object $context the context.
3355  * @param boolean $withprefix whether to prefix the name of the context with the
3356  *      type of context, e.g. User, Course, Forum, etc.
3357  * @param boolean $short whether to user the short name of the thing. Only applies
3358  *      to course contexts
3359  * @return string the human readable context name.
3360  */
3361 function print_context_name($context, $withprefix = true, $short = false) {
3362     global $DB;
3364     $name = '';
3365     switch ($context->contextlevel) {
3367         case CONTEXT_SYSTEM:
3368             $name = get_string('coresystem');
3369             break;
3371         case CONTEXT_USER:
3372             if ($user = $DB->get_record('user', array('id'=>$context->instanceid))) {
3373                 if ($withprefix){
3374                     $name = get_string('user').': ';
3375                 }
3376                 $name .= fullname($user);
3377             }
3378             break;
3380         case CONTEXT_COURSECAT:
3381             if ($category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) {
3382                 if ($withprefix){
3383                     $name = get_string('category').': ';
3384                 }
3385                 $name .=format_string($category->name);
3386             }
3387             break;
3389         case CONTEXT_COURSE:
3390             if ($context->instanceid == SITEID) {
3391                 $name = get_string('frontpage', 'admin');
3392             } else {
3393                 if ($course = $DB->get_record('course', array('id'=>$context->instanceid))) {
3394                     if ($withprefix){
3395                         $name = get_string('course').': ';
3396                     }
3397                     if ($short){
3398                         $name .= format_string($course->shortname);
3399                     } else {
3400                         $name .= format_string($course->fullname);
3401                    }
3402                 }
3403             }
3404             break;
3406         case CONTEXT_MODULE:
3407             if ($cm = $DB->get_record_sql('SELECT cm.*, md.name AS modname FROM {course_modules} cm ' .
3408                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3409                 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
3410                         if ($withprefix){
3411                         $name = get_string('modulename', $cm->modname).': ';
3412                         }
3413                         $name .= $mod->name;
3414                     }
3415                 }
3416             break;
3418         case CONTEXT_BLOCK:
3419             if ($blockinstance = $DB->get_record('block_instances', array('id'=>$context->instanceid))) {
3420                 global $CFG;
3421                 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3422                 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
3423                 $blockname = "block_$blockinstance->blockname";
3424                 if ($blockobject = new $blockname()) {
3425                     if ($withprefix){
3426                         $name = get_string('block').': ';
3427                     }
3428                     $name .= $blockobject->title;
3429                 }
3430             }
3431             break;
3433         default:
3434             print_error('unknowncontext');
3435             return false;
3436     }
3438     return $name;
3441 /**
3442  * Get a URL for a context, if there is a natural one. For example, for
3443  * CONTEXT_COURSE, this is the course page. For CONTEXT_USER it is the
3444  * user profile page.
3445  *
3446  * @param object $context the context.
3447  * @return moodle_url
3448  */
3449 function get_context_url($context) {
3450     global $COURSE, $DB;
3452     switch ($context->contextlevel) {
3453         case CONTEXT_USER:
3454             if ($COURSE->id == SITEID) {
3455                 $url = new moodle_url('/user/profile.php', array('id'=>$context->instanceid));
3456             } else {
3457                 $url = new moodle_url('/user/view.php', array('id'=>$context->instanceid, 'courseid'=>$COURSE->id));
3458             }
3459             return $url;;
3461         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
3462             return new moodle_url('/course/category.php', array('id'=>$context->instanceid));
3464         case CONTEXT_COURSE: // 1 to 1 to course cat
3465             if ($context->instanceid != SITEID) {
3466                 return new moodle_url('/course/view.php', array('id'=>$context->instanceid));
3467             }
3468             break;
3470         case CONTEXT_MODULE: // 1 to 1 to course
3471             if ($modname = $DB->get_field_sql('SELECT md.name AS modname FROM {course_modules} cm ' .
3472                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3473                 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$context->instanceid));
3474             }
3475             break;
3477         case CONTEXT_BLOCK:
3478             $parentcontexts = get_parent_contexts($context, false);
3479             $parent = reset($parentcontexts);
3480             $parent = get_context_instance_by_id($parent);
3481             return get_context_url($parent);
3482     }
3484     return new moodle_url('/');
3487 /**
3488  * Returns an array of all the known types of risk
3489  * The array keys can be used, for example as CSS class names, or in calls to
3490  * print_risk_icon. The values are the corresponding RISK_ constants.
3491  *
3492  * @return array all the known types of risk.
3493  */
3494 function get_all_risks() {
3495     return array(
3496         'riskmanagetrust' => RISK_MANAGETRUST,
3497         'riskconfig' => RISK_CONFIG,
3498         'riskxss' => RISK_XSS,
3499         'riskpersonal' => RISK_PERSONAL,
3500         'riskspam' => RISK_SPAM,
3501         'riskdataloss' => RISK_DATALOSS,
3502     );
3505 /**
3506  * Return a link to moodle docs for a given capability name
3507  *
3508  * @global object
3509  * @param object $capability a capability - a row from the mdl_capabilities table.
3510  * @return string the human-readable capability name as a link to Moodle Docs.
3511  */
3512 function get_capability_docs_link($capability) {
3513     global $CFG;
3514     $url = get_docs_url('Capabilities/' . $capability->name);
3515     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
3518 /**
3519  * Extracts the relevant capabilities given a contextid.
3520  * All case based, example an instance of forum context.
3521  * Will fetch all forum related capabilities, while course contexts
3522  * Will fetch all capabilities
3523  *
3524  * capabilities
3525  * `name` varchar(150) NOT NULL,
3526  * `captype` varchar(50) NOT NULL,
3527  * `contextlevel` int(10) NOT NULL,
3528  * `component` varchar(100) NOT NULL,
3529  *
3530  * @global object
3531  * @global object
3532  * @param object context
3533  * @return array
3534  */
3535 function fetch_context_capabilities($context) {
3536     global $DB, $CFG;
3538     $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
3540     $params = array();
3542     switch ($context->contextlevel) {
3544         case CONTEXT_SYSTEM: // all
3545             $SQL = "SELECT *
3546                       FROM {capabilities}";
3547         break;
3549         case CONTEXT_USER:
3550             $extracaps = array('moodle/grade:viewall');
3551             list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3552             $SQL = "SELECT *
3553                       FROM {capabilities}
3554                      WHERE contextlevel = ".CONTEXT_USER."
3555                            OR name $extra";
3556         break;
3558         case CONTEXT_COURSECAT: // course category context and bellow
3559             $SQL = "SELECT *
3560                       FROM {capabilities}
3561                      WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3562         break;
3564         case CONTEXT_COURSE: // course context and bellow
3565             $SQL = "SELECT *
3566                       FROM {capabilities}
3567                      WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3568         break;
3570         case CONTEXT_MODULE: // mod caps
3571             $cm = $DB->get_record('course_modules', array('id'=>$context->instanceid));
3572             $module = $DB->get_record('modules', array('id'=>$cm->module));
3574             $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
3575             if (file_exists($modfile)) {
3576                 include_once($modfile);
3577                 $modfunction = $module->name.'_get_extra_capabilities';
3578                 if (function_exists($modfunction)) {
3579                     $extracaps = $modfunction();
3580                 }
3581             }
3582             if(empty($extracaps)) {
3583                 $extracaps = array();
3584             }
3586             // All modules allow viewhiddenactivities. This is so you can hide
3587             // the module then override to allow specific roles to see it.
3588             // The actual check is in course page so not module-specific
3589             $extracaps[]="moodle/course:viewhiddenactivities";
3590             list($extra, $params) = $DB->get_in_or_equal(
3591                 $extracaps, SQL_PARAMS_NAMED, 'cap0');
3592             $extra = "OR name $extra";
3594             $SQL = "SELECT *
3595                       FROM {capabilities}
3596                      WHERE (contextlevel = ".CONTEXT_MODULE."
3597                            AND component = :component)
3598                            $extra";
3599             $params['component'] = "mod_$module->name";
3600         break;
3602         case CONTEXT_BLOCK: // block caps
3603             $bi = $DB->get_record('block_instances', array('id' => $context->instanceid));
3605             $extra = '';
3606             $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
3607             if ($extracaps) {
3608                 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3609                 $extra = "OR name $extra";
3610             }
3612             $SQL = "SELECT *
3613                       FROM {capabilities}
3614                      WHERE (contextlevel = ".CONTEXT_BLOCK."
3615                            AND component = :component)
3616                            $extra";
3617             $params['component'] = 'block_' . $bi->blockname;
3618         break;
3620         default:
3621         return false;
3622     }
3624     if (!$records = $DB->get_records_sql($SQL.' '.$sort, $params)) {
3625         $records = array();
3626     }
3628     return $records;
3632 /**
3633  * This fun