567395d63034a4bf754f6786e2ab8fad4ef17c30
[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 sytem 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);
172 /** size limit for context cache */
173 if (!defined('MAX_CONTEXT_CACHE_SIZE')) {
174     define('MAX_CONTEXT_CACHE_SIZE', 5000);
177 /**
178  * Although this looks like a global variable, it isn't really.
179  *
180  * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
181  * It is used to cache various bits of data between function calls for performance reasons.
182  * Sadly, a PHP global variale is the only way to impleemnt this, withough rewriting everything
183  * as methods of a class, instead of functions.
184  *
185  * @global stdClass $ACCESSLIB_PRIVATE
186  * @name $ACCESSLIB_PRIVATE
187  */
188 $ACCESSLIB_PRIVATE = new stdClass;
189 $ACCESSLIB_PRIVATE->contexts = array(); // Cache of context objects by level and instance
190 $ACCESSLIB_PRIVATE->contextsbyid = array(); // Cache of context objects by id
191 $ACCESSLIB_PRIVATE->systemcontext = null; // Used in get_system_context
192 $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache
193 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER
194 $ACCESSLIB_PRIVATE->roledefinitions = array(); // role definitions cache - helps a lot with mem usage in cron
195 $ACCESSLIB_PRIVATE->croncache = array(); // Used in get_role_access
196 $ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts.
197 $ACCESSLIB_PRIVATE->capabilitynames = null; // Used in is_valid_capability (only in developer debug mode)
199 /**
200  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
201  *
202  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
203  * accesslib's private caches. You need to do this before setting up test data,
204  * and also at the end fo the tests.
205  * @global object
206  * @global object
207  * @global object
208  */
209 function accesslib_clear_all_caches_for_unit_testing() {
210     global $UNITTEST, $USER, $ACCESSLIB_PRIVATE;
211     if (empty($UNITTEST->running)) {
212         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
213     }
214     $ACCESSLIB_PRIVATE->contexts = array();
215     $ACCESSLIB_PRIVATE->contextsbyid = array();
216     $ACCESSLIB_PRIVATE->systemcontext = null;
217     $ACCESSLIB_PRIVATE->dirtycontexts = null;
218     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
219     $ACCESSLIB_PRIVATE->roledefinitions = array();
220     $ACCESSLIB_PRIVATE->croncache = array();
221     $ACCESSLIB_PRIVATE->preloadedcourses = array();
222     $ACCESSLIB_PRIVATE->capabilitynames = null;
224     unset($USER->access);
227 /**
228  * Private function. Add a context object to accesslib's caches.
229  * @global object
230  * @param object $context
231  */
232 function cache_context($context) {
233     global $ACCESSLIB_PRIVATE;
235     // If there are too many items in the cache already, remove items until
236     // there is space
237     while (count($ACCESSLIB_PRIVATE->contextsbyid) >= MAX_CONTEXT_CACHE_SIZE) {
238         $first = reset($ACCESSLIB_PRIVATE->contextsbyid);
239         unset($ACCESSLIB_PRIVATE->contextsbyid[$first->id]);
240         unset($ACCESSLIB_PRIVATE->contexts[$first->contextlevel][$first->instanceid]);
241     }
243     $ACCESSLIB_PRIVATE->contexts[$context->contextlevel][$context->instanceid] = $context;
244     $ACCESSLIB_PRIVATE->contextsbyid[$context->id] = $context;
247 /**
248  * This is really slow!!! do not use above course context level
249  *
250  * @global object
251  * @param int $roleid
252  * @param object $context
253  * @return array
254  */
255 function get_role_context_caps($roleid, $context) {
256     global $DB;
258     //this is really slow!!!! - do not use above course context level!
259     $result = array();
260     $result[$context->id] = array();
262     // first emulate the parent context capabilities merging into context
263     $searchcontexts = array_reverse(get_parent_contexts($context));
264     array_push($searchcontexts, $context->id);
265     foreach ($searchcontexts as $cid) {
266         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
267             foreach ($capabilities as $cap) {
268                 if (!array_key_exists($cap->capability, $result[$context->id])) {
269                     $result[$context->id][$cap->capability] = 0;
270                 }
271                 $result[$context->id][$cap->capability] += $cap->permission;
272             }
273         }
274     }
276     // now go through the contexts bellow given context
277     $searchcontexts = array_keys(get_child_contexts($context));
278     foreach ($searchcontexts as $cid) {
279         if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
280             foreach ($capabilities as $cap) {
281                 if (!array_key_exists($cap->contextid, $result)) {
282                     $result[$cap->contextid] = array();
283                 }
284                 $result[$cap->contextid][$cap->capability] = $cap->permission;
285             }
286         }
287     }
289     return $result;
292 /**
293  * Gets the accessdata for role "sitewide" (system down to course)
294  *
295  * @global object
296  * @global object
297  * @param int $roleid
298  * @param array $accessdata defaults to null
299  * @return array
300  */
301 function get_role_access($roleid, $accessdata=NULL) {
303     global $CFG, $DB;
305     /* Get it in 1 cheap DB query...
306      * - relevant role caps at the root and down
307      *   to the course level - but not below
308      */
309     if (is_null($accessdata)) {
310         $accessdata           = array(); // named list
311         $accessdata['ra']     = array();
312         $accessdata['rdef']   = array();
313         $accessdata['loaded'] = array();
314     }
316     //
317     // Overrides for the role IN ANY CONTEXTS
318     // down to COURSE - not below -
319     //
320     $sql = "SELECT ctx.path,
321                    rc.capability, rc.permission
322               FROM {context} ctx
323               JOIN {role_capabilities} rc
324                    ON rc.contextid=ctx.id
325              WHERE rc.roleid = ?
326                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
327           ORDER BY ctx.depth, ctx.path";
328     $params = array($roleid);
330     // we need extra caching in CLI scripts and cron
331     if (CLI_SCRIPT) {
332         global $ACCESSLIB_PRIVATE;
334         if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) {
335             $ACCESSLIB_PRIVATE->croncache[$roleid] = array();
336             if ($rs = $DB->get_recordset_sql($sql, $params)) {
337                 foreach ($rs as $rd) {
338                     $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd;
339                 }
340                 $rs->close();
341             }
342         }
344         foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) {
345             $k = "{$rd->path}:{$roleid}";
346             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
347         }
349     } else {
350         if ($rs = $DB->get_recordset_sql($sql, $params)) {
351             foreach ($rs as $rd) {
352                 $k = "{$rd->path}:{$roleid}";
353                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
354             }
355             unset($rd);
356             $rs->close();
357         }
358     }
360     return $accessdata;
363 /**
364  * Gets the accessdata for role "sitewide" (system down to course)
365  *
366  * @global object
367  * @global object
368  * @param int $roleid
369  * @param array $accessdata defaults to null
370  * @return array
371  */
372 function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
374     global $CFG, $DB;
376     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
377     $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
379     //
380     // Overrides for the role in any contexts related to the course
381     //
382     $sql = "SELECT ctx.path,
383                    rc.capability, rc.permission
384               FROM {context} ctx
385               JOIN {role_capabilities} rc
386                    ON rc.contextid=ctx.id
387              WHERE rc.roleid = ?
388                    AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?)
389                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
390           ORDER BY ctx.depth, ctx.path";
391     $params = array($roleid, "$base/%");
393     if ($rs = $DB->get_recordset_sql($sql, $params)) {
394         foreach ($rs as $rd) {
395             $k = "{$rd->path}:{$roleid}";
396             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
397         }
398         unset($rd);
399         $rs->close();
400     }
402     return $accessdata;
406 /**
407  * Get the default guest role
408  *
409  * @global object
410  * @global object
411  * @return object role
412  */
413 function get_guest_role() {
414     global $CFG, $DB;
416     if (empty($CFG->guestroleid)) {
417         if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW)) {
418             $guestrole = array_shift($roles);   // Pick the first one
419             set_config('guestroleid', $guestrole->id);
420             return $guestrole;
421         } else {
422             debugging('Can not find any guest role!');
423             return false;
424         }
425     } else {
426         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
427             return $guestrole;
428         } else {
429             //somebody is messing with guest roles, remove incorrect setting and try to find a new one
430             set_config('guestroleid', '');
431             return get_guest_role();
432         }
433     }
436 /**
437  * Check whether a user has a paritcular capability in a given context.
438  *
439  * For example::
440  *      $context = get_context_instance(CONTEXT_MODULE, $cm->id);
441  *      has_capability('mod/forum:replypost',$context)
442  *
443  * By default checks the capabilties of the current user, but you can pass a
444  * different userid. By default will return true for admin-like users who have the
445  * moodle/site:doanything capability, but you can override that with the fourth argument.
446  *
447  * @param string $capability the name of the capability to check. For example mod/forum:view
448  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
449  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
450  * @param boolean $doanything If false, ignore the special moodle/site:doanything capability that admin-like roles have.
451  * @return boolean true if the user has this capability. Otherwise false.
452  */
453 function has_capability($capability, $context, $userid=NULL, $doanything=true) {
454     global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE;
456     if (during_initial_install()) {
457         if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") {
458             // we are in an installer - roles can not work yet
459             return true;
460         } else {
461             return false;
462         }
463     }
465     // the original $CONTEXT here was hiding serious errors
466     // for security reasons do not reuse previous context
467     if (empty($context)) {
468         debugging('Incorrect context specified');
469         return false;
470     }
472 /// Some sanity checks
473     if (debugging('',DEBUG_DEVELOPER)) {
474         if (!is_valid_capability($capability)) {
475             debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
476         }
477         if (!is_bool($doanything)) {
478             debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.');
479         }
480     }
482     if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid
483         if (empty($USER->id)) {
484             // Session not set up yet.
485             $userid = 0;
486         } else {
487             $userid = $USER->id;
488         }
489     }
491     if (is_null($context->path) or $context->depth == 0) {
492         //this should not happen
493         $contexts = array(SYSCONTEXTID, $context->id);
494         $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
495         debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
497     } else {
498         $contexts = explode('/', $context->path);
499         array_shift($contexts);
500     }
502     if (CLI_SCRIPT && !isset($USER->access)) {
503         // In cron, some modules setup a 'fake' $USER,
504         // ensure we load the appropriate accessdata.
505         if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
506             $ACCESSLIB_PRIVATE->dirtycontexts = NULL; //load fresh dirty contexts
507         } else {
508             load_user_accessdata($userid);
509             $ACCESSLIB_PRIVATE->dirtycontexts = array();
510         }
511         $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
513     } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) {
514         // caps not loaded yet - better to load them to keep BC with 1.8
515         // not-logged-in user or $USER object set up manually first time here
516         load_all_capabilities();
517         $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now
518         $ACCESSLIB_PRIVATE->roledefinitions = array();
519     }
521     // Load dirty contexts list if needed
522     if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
523         if (isset($USER->access['time'])) {
524             $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']);
525         }
526         else {
527             $ACCESSLIB_PRIVATE->dirtycontexts = array();
528         }
529     }
531     // Careful check for staleness...
532     if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) {
533         // reload all capabilities - preserving loginas, roleswitches, etc
534         // and then cleanup any marks of dirtyness... at least from our short
535         // term memory! :-)
536         $ACCESSLIB_PRIVATE->accessdatabyuser = array();
537         $ACCESSLIB_PRIVATE->roledefinitions = array();
539         if (CLI_SCRIPT) {
540             load_user_accessdata($userid);
541             $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
542             $ACCESSLIB_PRIVATE->dirtycontexts = array();
544         } else {
545             reload_all_capabilities();
546         }
547     }
549     // divulge how many times we are called
550     //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
552     if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid
553         //
554         // For the logged in user, we have $USER->access
555         // which will have all RAs and caps preloaded for
556         // course and above contexts.
557         //
558         // Contexts below courses && contexts that do not
559         // hang from courses are loaded into $USER->access
560         // on demand, and listed in $USER->access[loaded]
561         //
562         if ($context->contextlevel <= CONTEXT_COURSE) {
563             // Course and above are always preloaded
564             return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
565         }
566         // Load accessdata for below-the-course contexts
567         if (!path_inaccessdata($context->path,$USER->access)) {
568             // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
569             // $bt = debug_backtrace();
570             // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
571             load_subcontext($USER->id, $context, $USER->access);
572         }
573         return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
574     }
576     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
577         load_user_accessdata($userid);
578     }
579     if ($context->contextlevel <= CONTEXT_COURSE) {
580         // Course and above are always preloaded
581         return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid], $doanything);
582     }
583     // Load accessdata for below-the-course contexts as needed
584     if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
585         // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
586         // $bt = debug_backtrace();
587         // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
588         load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
589     }
590     return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid], $doanything);
593 /**
594  * Check if the user has any one of several capabilities from a list.
595  *
596  * This is just a utility method that calls has_capability in a loop. Try to put
597  * the capabilities that most users are likely to have first in the list for best
598  * performance.
599  *
600  * There are probably tricks that could be done to improve the performance here, for example,
601  * check the capabilities that are already cached first.
602  *
603  * @see has_capability()
604  * @param array $capabilities an array of capability names.
605  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
606  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
607  * @param boolean $doanything If false, ignore the special moodle/site:doanything capability that admin-like roles have.
608  * @return boolean true if the user has any of these capabilities. Otherwise false.
609  */
610 function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
611     if (!is_array($capabilities)) {
612         debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array');
613         return false;
614     }
615     foreach ($capabilities as $capability) {
616         if (has_capability($capability, $context, $userid, $doanything)) {
617             return true;
618         }
619     }
620     return false;
623 /**
624  * Check if the user has all the capabilities in a list.
625  *
626  * This is just a utility method that calls has_capability in a loop. Try to put
627  * the capabilities that fewest users are likely to have first in the list for best
628  * performance.
629  *
630  * There are probably tricks that could be done to improve the performance here, for example,
631  * check the capabilities that are already cached first.
632  *
633  * @see has_capability()
634  * @param array $capabilities an array of capability names.
635  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
636  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
637  * @param boolean $doanything If false, ignore the special moodle/site:doanything capability that admin-like roles have.
638  * @return boolean true if the user has all of these capabilities. Otherwise false.
639  */
640 function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) {
641     if (!is_array($capabilities)) {
642         debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
643         return false;
644     }
645     foreach ($capabilities as $capability) {
646         if (!has_capability($capability, $context, $userid, $doanything)) {
647             return false;
648         }
649     }
650     return true;
653 /**
654  * Check if the user is an admin at the site level
655  *
656  * Uses 1 DB query to answer whether a user is an admin at the sitelevel.
657  * It depends on DB schema >=1.7 but does not depend on the new datastructures
658  * in v1.9 (context.path, or $USER->access)
659  *
660  * Will return true if the userid has any of
661  *  - moodle/site:config
662  *  - moodle/legacy:admin
663  *  - moodle/site:doanything
664  *
665  * @global object
666  * @global object
667  * @param   int  $userid
668  * @returns bool true is user can administer server settings
669  */
670 function is_siteadmin($userid) {
671     global $CFG, $DB;
673     $sql = "SELECT SUM(rc.permission)
674               FROM {role_capabilities} rc
675               JOIN {context} ctx
676                    ON ctx.id=rc.contextid
677               JOIN {role_assignments} ra
678                    ON ra.roleid=rc.roleid AND ra.contextid=ctx.id
679              WHERE ctx.contextlevel=10
680                    AND ra.userid=?
681                    AND rc.capability IN (?, ?, ?)
682           GROUP BY rc.capability
683             HAVING SUM(rc.permission) > 0";
684     $params = array($userid, 'moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything');
686     return $DB->record_exists_sql($sql, $params);
689 /**
690  * Check whether a role is an admin at the site level
691  *
692  * Will return true if the userid has any of
693  *  - moodle/site:config
694  *  - moodle/legacy:admin
695  *  - moodle/site:doanything
696  *
697  * @global object
698  * @param integer $roleid a role id.
699  * @return boolean, whether this role is an admin role.
700  */
701 function is_admin_role($roleid) {
702     global $DB;
704     $sql = "SELECT 1
705               FROM {role_capabilities} rc
706               JOIN {context} ctx ON ctx.id = rc.contextid
707              WHERE ctx.contextlevel = 10
708                    AND rc.roleid = ?
709                    AND rc.capability IN (?, ?, ?)
710           GROUP BY rc.capability
711             HAVING SUM(rc.permission) > 0";
712     $params = array($roleid, 'moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything');
714     return $DB->record_exists_sql($sql, $params);
717 /**
718  * Returns all the roles for which is_admin_role($role->id) is true.
719  *
720  * @global object
721  * @return array
722  */
723 function get_admin_roles() {
724     global $DB;
726     $sql = "SELECT *
727               FROM {role} r
728              WHERE EXISTS (
729                     SELECT 1
730                       FROM {role_capabilities} rc
731                       JOIN {context} ctx ON ctx.id = rc.contextid
732                      WHERE ctx.contextlevel = 10
733                            AND rc.roleid = r.id
734                            AND rc.capability IN (?, ?, ?)
735                   GROUP BY rc.capability
736                     HAVING SUM(rc.permission) > 0
737              )
738           ORDER BY r.sortorder";
739     $params = array('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything');
741     return $DB->get_records_sql($sql, $params);
744 /**
745  * @param string $path
746  * @return string
747  */
748 function get_course_from_path ($path) {
749     // assume that nothing is more than 1 course deep
750     if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
751         return $matches[1];
752     }
753     return false;
756 /**
757  * @param string $path
758  * @param array $accessdata
759  * @return bool
760  */
761 function path_inaccessdata($path, $accessdata) {
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  * Originaly there was an extremely complicated way
810  * to determine the user access that dealt with
811  * "locality" or role assignemnts and role overrides.
812  * Now we simply evaluate access for each roel 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  * Incorrectly set Guest role as Default user role
817  * -----------------------------------------------
818  * Admins have to make sure that the "Default user role" does
819  * not have 'moodle/course:view' or 'moodle/legacy:guest'!
820  *
821  * Incorrectly set Frontpage role
822  * ------------------------------
823  * Admins have to make sure that the "Frontpage role" does
824  * not have 'moodle/legacy:guest'.
825  *
826  * @param string $capability
827  * @param object $context
828  * @param array $accessdata
829  * @param bool $doanything
830  * @return bool
831  */
832 function has_capability_in_accessdata($capability, $context, array $accessdata, $doanything) {
833     global $CFG;
835     if (empty($context->id)) {
836         throw new coding_exception('Invalid context specified');
837     }
839     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
840     $contextids = explode('/', trim($context->path, '/'));
841     $paths = array($context->path);
842     while ($contextids) {
843         array_pop($contextids);
844         $paths[] = '/' . implode('/', $contextids);
845     }
846     unset($contextids);
848     if ($doanything and strpos($capability, 'moodle/legacy:') === 0) {
849         // admins do not have any legacy capabilities
850         $doanything = false;
851     }
853     $roles = array();
854     $switchedrole = false;
856     // Find out if role switched
857     if (!empty($accessdata['rsw'])) {
858         // From the bottom up...
859         foreach ($paths as $path) {
860             if (isset($accessdata['rsw'][$ctxp])) {
861                 // Found a switchrole assignment - check for that role _plus_ the default user role
862                 $roles = array($accessdata['rsw'][$ctxp]=>null, $CFG->defaultuserroleid=>null);
863                 $switchedrole = true;
864                 break;
865             }
866         }
867     }
869     if (!$switchedrole) {
870         // get all users roles in this context and above
871         foreach ($paths as $path) {
872             if (isset($accessdata['ra'][$path])) {
873                 foreach ($accessdata['ra'][$path] as $roleid) {
874                     $roles[$roleid] = null;
875                 }
876             }
877         }
879         // Find out if user is admin - it is not possible to override the doanything in any way
880         // and it is not possible to switch to admin role either.
881         if ($doanything or $capability === 'moodle/site:doanything') {
882             $systempath = '/'.SYSCONTEXTID;
883             foreach ($roles as $roleid=>$ignored) {
884                 if (isset($accessdata['rdef']["{$systempath}:$roleid"]['moodle/site:doanything']) and $accessdata['rdef']["{$systempath}:$roleid"]['moodle/site:doanything'] == CAP_ALLOW) {
885                     return true;
886                 }
887             }
888             if ($capability === 'moodle/site:doanything') {
889                 // do anything can not be overridden, prevented or prohibited
890                 return false;
891             }
892         }
893     }
895     // Now find out what access is given to each role, going bottom-->up direction
896     foreach ($roles as $roleid => $ignored) {
897         foreach ($paths as $path) {
898             if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
899                 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
900                 if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) {
901                     $roles[$roleid] = $perm;
902                 }
903             }
904         }
905     }
906     // any CAP_PROHIBIT found means no permission for the user
907     if (array_search(CAP_PROHIBIT, $roles) !== false) {
908         return false;
909     }
911     // at least one CAP_ALLOW means the user has a permission
912     return (array_search(CAP_ALLOW, $roles) !== false);
915 /**
916  * @param object $context
917  * @param array $accessdata
918  * @return array
919  */
920 function aggregate_roles_from_accessdata($context, $accessdata) {
922     $path = $context->path;
924     // build $contexts as a list of "paths" of the current
925     // contexts and parents with the order top-to-bottom
926     $contexts = array($path);
927     while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
928         $path = $matches[1];
929         array_unshift($contexts, $path);
930     }
932     $cc = count($contexts);
934     $roles = array();
935     // From the bottom up...
936     for ($n=$cc-1;$n>=0;$n--) {
937         $ctxp = $contexts[$n];
938         if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
939             // Found assignments on this leaf
940             $addroles = $accessdata['ra'][$ctxp];
941             $roles    = array_merge($roles, $addroles);
942         }
943     }
945     return array_unique($roles);
948 /**
949  * A convenience function that tests has_capability, and displays an error if
950  * the user does not have that capability.
951  *
952  * NOTE before Moodle 2.0, this function attempted to make an appropriate
953  * require_login call before checking the capability. This is no longer the case.
954  * You must call require_login (or one of its variants) if you want to check the
955  * user is logged in, before you call this function.
956  *
957  * @see has_capability()
958  *
959  * @param string $capability the name of the capability to check. For example mod/forum:view
960  * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}.
961  * @param integer $userid A user id. By default (null) checks the permissions of the current user.
962  * @param bool $doanything If false, ignore the special moodle/site:doanything capability that admin-like roles have.
963  * @param string $errorstring The error string to to user. Defaults to 'nopermissions'.
964  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
965  * @return void terminates with an error if the user does not have the given capability.
966  */
967 function require_capability($capability, $context, $userid = NULL, $doanything = true,
968                             $errormessage = 'nopermissions', $stringfile = '') {
969     if (!has_capability($capability, $context, $userid, $doanything)) {
970         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
971     }
974 /**
975  * Get an array of courses where cap requested is available
976  *
977  * Get an array of courses (with magic extra bits)
978  * where the accessdata and in DB enrolments show
979  * that the cap requested is available.
980  *
981  * The main use is for get_my_courses().
982  *
983  * Notes
984  *
985  * - $fields is an array of fieldnames to ADD
986  *   so name the fields you really need, which will
987  *   be added and uniq'd
988  *
989  * - the course records have $c->context which is a fully
990  *   valid context object. Saves you a query per course!
991  *
992  * - the course records have $c->categorypath to make
993  *   category lookups cheap
994  *
995  * - current implementation is split in -
996  *
997  *   - if the user has the cap systemwide, stupidly
998  *     grab *every* course for a capcheck. This eats
999  *     a TON of bandwidth, specially on large sites
1000  *     with separate DBs...
1001  *
1002  *   - otherwise, fetch "likely" courses with a wide net
1003  *     that should get us _cheaply_ at least the courses we need, and some
1004  *     we won't - we get courses that...
1005  *      - are in a category where user has the cap
1006  *      - or where use has a role-assignment (any kind)
1007  *      - or where the course has an override on for this cap
1008  *
1009  *   - walk the courses recordset checking the caps oneach one
1010  *     the checks are all in memory and quite fast
1011  *     (though we could implement a specialised variant of the
1012  *     has_capability_in_accessdata() code to speed it up)
1013  *
1014  * @global object
1015  * @global object
1016  * @param string $capability - name of the capability
1017  * @param array  $accessdata - accessdata session array
1018  * @param bool   $doanything - if false, ignore do anything
1019  * @param string $sort - sorting fields - prefix each fieldname with "c."
1020  * @param array  $fields - additional fields you are interested in...
1021  * @param int    $limit  - set if you want to limit the number of courses
1022  * @return array $courses - ordered array of course objects - see notes above
1023  */
1024 function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) {
1026     global $CFG, $DB;
1028     // Slim base fields, let callers ask for what they need...
1029     $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
1031     if (!is_null($fields)) {
1032         $fields = array_merge($basefields, $fields);
1033         $fields = array_unique($fields);
1034     } else {
1035         $fields = $basefields;
1036     }
1037     // If any of the fields is '*', leave it alone, discarding the rest
1038     // to avoid ambiguous columns under some silly DBs. See MDL-18746 :-D
1039     if (in_array('*', $fields)) {
1040         $fields = array('*');
1041     }
1042     $coursefields = 'c.' .implode(',c.', $fields);
1044     $sort = trim($sort);
1045     if ($sort !== '') {
1046         $sort = "ORDER BY $sort";
1047     }
1049     $sysctx = get_context_instance(CONTEXT_SYSTEM);
1050     if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
1051         //
1052         // Apparently the user has the cap sitewide, so walk *every* course
1053         // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
1054         // Yuck.
1055         //
1056         $sql = "SELECT $coursefields,
1057                        ctx.id AS ctxid, ctx.path AS ctxpath,
1058                        ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1059                        cc.path AS categorypath
1060                   FROM {course} c
1061                   JOIN {course_categories} cc
1062                        ON c.category=cc.id
1063                   JOIN {context} ctx
1064                        ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1065                  $sort ";
1066         $rs = $DB->get_recordset_sql($sql);
1067     } else {
1068         //
1069         // narrow down where we have the caps to a few contexts
1070         // this will be a combination of
1071         // - courses    where user has an explicit enrolment
1072         // - courses    that have an override (any status) on that capability
1073         // - categories where user has the rights (granted status) on that capability
1074         //
1075         $sql = "SELECT ctx.*
1076                   FROM {context} ctx
1077                  WHERE ctx.contextlevel=".CONTEXT_COURSECAT."
1078               ORDER BY ctx.depth";
1079         $rs = $DB->get_recordset_sql($sql);
1080         $catpaths = array();
1081         foreach ($rs as $catctx) {
1082             if ($catctx->path != ''
1083                 && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
1084                 $catpaths[] = $catctx->path;
1085             }
1086         }
1087         $rs->close();
1088         $catclause = '';
1089         $params = array();
1090         if (count($catpaths)) {
1091             $cc = count($catpaths);
1092             for ($n=0;$n<$cc;$n++) {
1093                 $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
1094             }
1095             $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')';
1096         }
1097         unset($catpaths);
1099         $capany = '';
1100         if ($doanything) {
1101             $capany = " OR rc.capability=:doany";
1102             $params['doany'] = 'moodle/site:doanything';
1103         }
1105         /// UNION 3 queries:
1106         /// - user role assignments in courses
1107         /// - user capability (override - any status) in courses
1108         /// - user right (granted status) in categories (optionally executed)
1109         /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
1110         /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
1111         $sql = "
1112             SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath
1113               FROM (
1114                     SELECT c.id,
1115                            ctx.id AS ctxid, ctx.path AS ctxpath,
1116                            ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1117                            cc.path AS categorypath
1118                     FROM {course} c
1119                     JOIN {course_categories} cc
1120                       ON c.category=cc.id
1121                     JOIN {context} ctx
1122                       ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1123                     JOIN {role_assignments} ra
1124                       ON (ra.contextid=ctx.id AND ra.userid=:userid)
1125                     UNION
1126                     SELECT c.id,
1127                            ctx.id AS ctxid, ctx.path AS ctxpath,
1128                            ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1129                            cc.path AS categorypath
1130                     FROM {course} c
1131                     JOIN {course_categories} cc
1132                       ON c.category=cc.id
1133                     JOIN {context} ctx
1134                       ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1135                     JOIN {role_capabilities} rc
1136                       ON (rc.contextid=ctx.id AND (rc.capability=:cap $capany)) ";
1138         if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too
1139             $sql .= "
1140                     UNION
1141                     SELECT c.id,
1142                            ctx.id AS ctxid, ctx.path AS ctxpath,
1143                            ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1144                            cc.path AS categorypath
1145                     FROM {course} c
1146                     JOIN {course_categories} cc
1147                       ON c.category=cc.id
1148                     JOIN {context} ctx
1149                       ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1150                     $catclause";
1151         }
1153     /// Close the inline_view and join with courses table to get requested $coursefields
1154         $sql .= "
1155                 ) inline_view
1156                 INNER JOIN {course} c
1157                     ON inline_view.id = c.id";
1159     /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
1160         $sql .= "
1161                 " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause
1163         $params['userid'] = $userid;
1164         $params['cap']    = $cap;
1165         $rs = $DB->get_recordset_sql($sql, $params);
1166     }
1168 /// Confirm rights (granted capability) for each course returned
1169     $courses = array();
1170     $cc = 0; // keep count
1171     if ($rs) {
1172         foreach ($rs as $c) {
1173             // build the context obj
1174             $c = make_context_subobj($c);
1176             if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) {
1177                 if ($limit > 0 && $cc >= $limit) {
1178                     break;
1179                 }
1181                 $courses[] = $c;
1182                 $cc++;
1183             }
1184         }
1185         $rs->close();
1186     }
1188     return $courses;
1192 /**
1193  * Return a nested array showing role assignments
1194  * all relevant role capabilities for the user at
1195  * site/metacourse/course_category/course levels
1196  *
1197  * We do _not_ delve deeper than courses because the number of
1198  * overrides at the module/block levels is HUGE.
1199  *
1200  * [ra]   => [/path/][]=roleid
1201  * [rdef] => [/path/:roleid][capability]=permission
1202  * [loaded] => array('/path', '/path')
1203  *
1204  * @global object
1205  * @global object
1206  * @param $userid integer - the id of the user
1207  */
1208 function get_user_access_sitewide($userid) {
1210     global $CFG, $DB;
1212     /* Get in 3 cheap DB queries...
1213      * - role assignments
1214      * - relevant role caps
1215      *   - above and within this user's RAs
1216      *   - below this user's RAs - limited to course level
1217      */
1219     $accessdata           = array(); // named list
1220     $accessdata['ra']     = array();
1221     $accessdata['rdef']   = array();
1222     $accessdata['loaded'] = array();
1224     //
1225     // Role assignments
1226     //
1227     $sql = "SELECT ctx.path, ra.roleid
1228               FROM {role_assignments} ra
1229               JOIN {context} ctx ON ctx.id=ra.contextid
1230              WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE;
1231     $params = array($userid);
1232     $rs = $DB->get_recordset_sql($sql, $params);
1234     //
1235     // raparents collects paths & roles we need to walk up
1236     // the parenthood to build the rdef
1237     //
1238     $raparents = array();
1239     if ($rs) {
1240         foreach ($rs as $ra) {
1241             // RAs leafs are arrays to support multi
1242             // role assignments...
1243             if (!isset($accessdata['ra'][$ra->path])) {
1244                 $accessdata['ra'][$ra->path] = array();
1245             }
1246             array_push($accessdata['ra'][$ra->path], $ra->roleid);
1248             // Concatenate as string the whole path (all related context)
1249             // for this role. This is damn faster than using array_merge()
1250             // Will unique them later
1251             if (isset($raparents[$ra->roleid])) {
1252                 $raparents[$ra->roleid] .= $ra->path;
1253             } else {
1254                 $raparents[$ra->roleid] = $ra->path;
1255             }
1256         }
1257         unset($ra);
1258         $rs->close();
1259     }
1261     // Walk up the tree to grab all the roledefs
1262     // of interest to our user...
1263     //
1264     // NOTE: we use a series of IN clauses here - which
1265     // might explode on huge sites with very convoluted nesting of
1266     // categories... - extremely unlikely that the number of categories
1267     // and roletypes is so large that we hit the limits of IN()
1268     $clauses = '';
1269     $cparams = array();
1270     foreach ($raparents as $roleid=>$strcontexts) {
1271         $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/'))));
1272         if ($contexts ==! '') {
1273             if ($clauses) {
1274                 $clauses .= ' OR ';
1275             }
1276             $clauses .= "(roleid=? AND contextid IN ($contexts))";
1277             $cparams[] = $roleid;
1278         }
1279     }
1281     if ($clauses !== '') {
1282         $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1283                 FROM {role_capabilities} rc
1284                 JOIN {context} ctx
1285                   ON rc.contextid=ctx.id
1286                 WHERE $clauses";
1288         unset($clauses);
1289         $rs = $DB->get_recordset_sql($sql, $cparams);
1291         if ($rs) {
1292             foreach ($rs as $rd) {
1293                 $k = "{$rd->path}:{$rd->roleid}";
1294                 $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1295             }
1296             unset($rd);
1297             $rs->close();
1298         }
1299     }
1301     //
1302     // Overrides for the role assignments IN SUBCONTEXTS
1303     // (though we still do _not_ go below the course level.
1304     //
1305     // NOTE that the JOIN w sctx is with 3-way triangulation to
1306     // catch overrides to the applicable role in any subcontext, based
1307     // on the path field of the parent.
1308     //
1309     $sql = "SELECT sctx.path, ra.roleid,
1310                    ctx.path AS parentpath,
1311                    rco.capability, rco.permission
1312               FROM {role_assignments} ra
1313               JOIN {context} ctx
1314                    ON ra.contextid=ctx.id
1315               JOIN {context} sctx
1316                    ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " )
1317               JOIN {role_capabilities} rco
1318                    ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1319              WHERE ra.userid = ?
1320                AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
1321                AND sctx.contextlevel <= ".CONTEXT_COURSE."
1322           ORDER BY sctx.depth, sctx.path, ra.roleid";
1323     $params = array($userid);
1324     $rs = $DB->get_recordset_sql($sql, $params);
1325     if ($rs) {
1326         foreach ($rs as $rd) {
1327             $k = "{$rd->path}:{$rd->roleid}";
1328             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1329         }
1330         unset($rd);
1331         $rs->close();
1332     }
1333     return $accessdata;
1336 /**
1337  * Add to the access ctrl array the data needed by a user for a given context
1338  *
1339  * @global object
1340  * @global object
1341  * @param integer $userid the id of the user
1342  * @param object $context needs path!
1343  * @param array $accessdata accessdata array
1344  */
1345 function load_subcontext($userid, $context, &$accessdata) {
1347     global $CFG, $DB;
1349     /* Get the additional RAs and relevant rolecaps
1350      * - role assignments - with role_caps
1351      * - relevant role caps
1352      *   - above this user's RAs
1353      *   - below this user's RAs - limited to course level
1354      */
1356     $base = "/" . SYSCONTEXTID;
1358     //
1359     // Replace $context with the target context we will
1360     // load. Normally, this will be a course context, but
1361     // may be a different top-level context.
1362     //
1363     // We have 3 cases
1364     //
1365     // - Course
1366     // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1367     // - BLOCK/MODULE/GROUP hanging from a course
1368     //
1369     // For course contexts, we _already_ have the RAs
1370     // but the cost of re-fetching is minimal so we don't care.
1371     //
1372     if ($context->contextlevel !== CONTEXT_COURSE
1373         && $context->path !== "$base/{$context->id}") {
1374         // Case BLOCK/MODULE/GROUP hanging from a course
1375         // Assumption: the course _must_ be our parent
1376         // If we ever see stuff nested further this needs to
1377         // change to do 1 query over the exploded path to
1378         // find out which one is the course
1379         $courses = explode('/',get_course_from_path($context->path));
1380         $targetid = array_pop($courses);
1381         $context = get_context_instance_by_id($targetid);
1383     }
1385     //
1386     // Role assignments in the context and below
1387     //
1388     $sql = "SELECT ctx.path, ra.roleid
1389               FROM {role_assignments} ra
1390               JOIN {context} ctx
1391                    ON ra.contextid=ctx.id
1392              WHERE ra.userid = ?
1393                    AND (ctx.path = ? OR ctx.path LIKE ?)
1394           ORDER BY ctx.depth, ctx.path, ra.roleid";
1395     $params = array($userid, $context->path, $context->path."/%");
1396     $rs = $DB->get_recordset_sql($sql, $params);
1398     //
1399     // Read in the RAs, preventing duplicates
1400     //
1401     if ($rs) {
1402         $localroles = array();
1403         $lastseen  = '';
1404         foreach ($rs as $ra) {
1405             if (!isset($accessdata['ra'][$ra->path])) {
1406                 $accessdata['ra'][$ra->path] = array();
1407             }
1408             // only add if is not a repeat caused
1409             // by capability join...
1410             // (this check is cheaper than in_array())
1411             if ($lastseen !== $ra->path.':'.$ra->roleid) {
1412                 $lastseen = $ra->path.':'.$ra->roleid;
1413                 array_push($accessdata['ra'][$ra->path], $ra->roleid);
1414                 array_push($localroles,           $ra->roleid);
1415             }
1416         }
1417         $rs->close();
1418     }
1420     //
1421     // Walk up and down the tree to grab all the roledefs
1422     // of interest to our user...
1423     //
1424     // NOTES
1425     // - we use IN() but the number of roles is very limited.
1426     //
1427     $courseroles    = aggregate_roles_from_accessdata($context, $accessdata);
1429     // Do we have any interesting "local" roles?
1430     $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1431     $wherelocalroles='';
1432     if (count($localroles)) {
1433         // Role defs for local roles in 'higher' contexts...
1434         $contexts = substr($context->path, 1); // kill leading slash
1435         $contexts = str_replace('/', ',', $contexts);
1436         $localroleids = implode(',',$localroles);
1437         $wherelocalroles="OR (rc.roleid IN ({$localroleids})
1438                               AND ctx.id IN ($contexts))" ;
1439     }
1441     // We will want overrides for all of them
1442     $whereroles = '';
1443     if ($roleids  = implode(',',array_merge($courseroles,$localroles))) {
1444         $whereroles = "rc.roleid IN ($roleids) AND";
1445     }
1446     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1447               FROM {role_capabilities} rc
1448               JOIN {context} ctx
1449                    ON rc.contextid=ctx.id
1450              WHERE ($whereroles
1451                     (ctx.id=? OR ctx.path LIKE ?))
1452                    $wherelocalroles
1453           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1454     $params = array($context->id, $context->path."/%");
1456     $newrdefs = array();
1457     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1458         foreach ($rs as $rd) {
1459             $k = "{$rd->path}:{$rd->roleid}";
1460             if (!array_key_exists($k, $newrdefs)) {
1461                 $newrdefs[$k] = array();
1462             }
1463             $newrdefs[$k][$rd->capability] = $rd->permission;
1464         }
1465         $rs->close();
1466     } else {
1467         debugging('Bad SQL encountered!');
1468     }
1470     compact_rdefs($newrdefs);
1471     foreach ($newrdefs as $key=>$value) {
1472         $accessdata['rdef'][$key] =& $newrdefs[$key];
1473     }
1475     // error_log("loaded {$context->path}");
1476     $accessdata['loaded'][] = $context->path;
1479 /**
1480  * Add to the access ctrl array the data needed by a role for a given context.
1481  *
1482  * The data is added in the rdef key.
1483  *
1484  * This role-centric function is useful for role_switching
1485  * and to get an overview of what a role gets under a
1486  * given context and below...
1487  *
1488  * @global object
1489  * @global object
1490  * @param integer $roleid the id of the user
1491  * @param object $context needs path!
1492  * @param array $accessdata accessdata array null by default
1493  * @return array
1494  */
1495 function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
1497     global $CFG, $DB;
1499     /* Get the relevant rolecaps into rdef
1500      * - relevant role caps
1501      *   - at ctx and above
1502      *   - below this ctx
1503      */
1505     if (is_null($accessdata)) {
1506         $accessdata           = array(); // named list
1507         $accessdata['ra']     = array();
1508         $accessdata['rdef']   = array();
1509         $accessdata['loaded'] = array();
1510     }
1512     $contexts = substr($context->path, 1); // kill leading slash
1513     $contexts = str_replace('/', ',', $contexts);
1515     //
1516     // Walk up and down the tree to grab all the roledefs
1517     // of interest to our role...
1518     //
1519     // NOTE: we use an IN clauses here - which
1520     // might explode on huge sites with very convoluted nesting of
1521     // categories... - extremely unlikely that the number of nested
1522     // categories is so large that we hit the limits of IN()
1523     //
1524     $sql = "SELECT ctx.path, rc.capability, rc.permission
1525               FROM {role_capabilities} rc
1526               JOIN {context} ctx
1527                    ON rc.contextid=ctx.id
1528              WHERE rc.roleid=? AND
1529                    ( ctx.id IN ($contexts) OR
1530                     ctx.path LIKE ? )
1531           ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1532     $params = array($roleid, $context->path."/%");
1534     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1535         foreach ($rs as $rd) {
1536             $k = "{$rd->path}:{$roleid}";
1537             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1538         }
1539         $rs->close();
1540     }
1542     return $accessdata;
1545 /**
1546  * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global
1547  *
1548  * Used by has_capability() - but feel free
1549  * to call it if you are about to run a BIG
1550  * cron run across a bazillion users.
1551  *
1552  * @global object
1553  * @global object
1554  * @param int $userid
1555  * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid]
1556  */
1557 function load_user_accessdata($userid) {
1558     global $CFG, $ACCESSLIB_PRIVATE;
1560     $base = '/'.SYSCONTEXTID;
1562     $accessdata = get_user_access_sitewide($userid);
1563     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1564     //
1565     // provide "default role" & set 'dr'
1566     //
1567     if (!empty($CFG->defaultuserroleid)) {
1568         $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1569         if (!isset($accessdata['ra'][$base])) {
1570             $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1571         } else {
1572             array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1573         }
1574         $accessdata['dr'] = $CFG->defaultuserroleid;
1575     }
1577     //
1578     // provide "default frontpage role"
1579     //
1580     if (!empty($CFG->defaultfrontpageroleid)) {
1581         $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1582         $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1583         if (!isset($accessdata['ra'][$base])) {
1584             $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1585         } else {
1586             array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1587         }
1588     }
1589     // for dirty timestamps in cron
1590     $accessdata['time'] = time();
1592     $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1593     compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']);
1595     return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1598 /**
1599  * Use shared copy of role definistions stored in ACCESSLIB_PRIVATE->roledefinitions;
1600  *
1601  * @global object
1602  * @param array $rdefs array of role definitions in contexts
1603  */
1604 function compact_rdefs(&$rdefs) {
1605     global $ACCESSLIB_PRIVATE;
1607     /*
1608      * This is a basic sharing only, we could also
1609      * use md5 sums of values. The main purpose is to
1610      * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array.
1611      */
1613     foreach ($rdefs as $key => $value) {
1614         if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) {
1615             $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key];
1616         }
1617         $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key];
1618     }
1621 /**
1622  * A convenience function to completely load all the capabilities
1623  * for the current user.   This is what gets called from complete_user_login()
1624  * for example. Call it only _after_ you've setup $USER and called
1625  * check_enrolment_plugins();
1626  * @see check_enrolment_plugins()
1627  *
1628  * @global object
1629  * @global object
1630  * @global object
1631  */
1632 function load_all_capabilities() {
1633     global $USER, $CFG, $ACCESSLIB_PRIVATE;
1635     // roles not installed yet - we are in the middle of installation
1636     if (during_initial_install()) {
1637         return;
1638     }
1640     $base = '/'.SYSCONTEXTID;
1642     if (isguestuser()) {
1643         $guest = get_guest_role();
1645         // Load the rdefs
1646         $USER->access = get_role_access($guest->id);
1647         // Put the ghost enrolment in place...
1648         $USER->access['ra'][$base] = array($guest->id);
1651     } else if (isloggedin()) {
1653         $accessdata = get_user_access_sitewide($USER->id);
1655         //
1656         // provide "default role" & set 'dr'
1657         //
1658         if (!empty($CFG->defaultuserroleid)) {
1659             $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1660             if (!isset($accessdata['ra'][$base])) {
1661                 $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1662             } else {
1663                 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1664             }
1665             $accessdata['dr'] = $CFG->defaultuserroleid;
1666         }
1668         $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1670         //
1671         // provide "default frontpage role"
1672         //
1673         if (!empty($CFG->defaultfrontpageroleid)) {
1674             $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1675             $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1676             if (!isset($accessdata['ra'][$base])) {
1677                 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1678             } else {
1679                 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1680             }
1681         }
1682         $USER->access = $accessdata;
1684     } else if (!empty($CFG->notloggedinroleid)) {
1685         $USER->access = get_role_access($CFG->notloggedinroleid);
1686         $USER->access['ra'][$base] = array($CFG->notloggedinroleid);
1687     }
1689     // Timestamp to read dirty context timestamps later
1690     $USER->access['time'] = time();
1691     $ACCESSLIB_PRIVATE->dirtycontexts = array();
1693     // Clear to force a refresh
1694     unset($USER->mycourses);
1697 /**
1698  * A convenience function to completely reload all the capabilities
1699  * for the current user when roles have been updated in a relevant
1700  * context -- but PRESERVING switchroles and loginas.
1701  *
1702  * That is - completely transparent to the user.
1703  *
1704  * Note: rewrites $USER->access completely.
1705  *
1706  * @global object
1707  * @global object
1708  */
1709 function reload_all_capabilities() {
1710     global $USER, $DB;
1712     // error_log("reloading");
1713     // copy switchroles
1714     $sw = array();
1715     if (isset($USER->access['rsw'])) {
1716         $sw = $USER->access['rsw'];
1717         // error_log(print_r($sw,1));
1718     }
1720     unset($USER->access);
1721     unset($USER->mycourses);
1723     load_all_capabilities();
1725     foreach ($sw as $path => $roleid) {
1726         $context = $DB->get_record('context', array('path'=>$path));
1727         role_switch($roleid, $context);
1728     }
1732 /**
1733  * Adds a temp role to an accessdata array.
1734  *
1735  * Useful for the "temporary guest" access
1736  * we grant to logged-in users.
1737  *
1738  * Note - assumes a course context!
1739  *
1740  * @global object
1741  * @global object
1742  * @param object $content
1743  * @param int $roleid
1744  * @param array $accessdata
1745  * @return array Returns access data
1746  */
1747 function load_temp_role($context, $roleid, $accessdata) {
1749     global $CFG, $DB;
1751     //
1752     // Load rdefs for the role in -
1753     // - this context
1754     // - all the parents
1755     // - and below - IOWs overrides...
1756     //
1758     // turn the path into a list of context ids
1759     $contexts = substr($context->path, 1); // kill leading slash
1760     $contexts = str_replace('/', ',', $contexts);
1762     $sql = "SELECT ctx.path, rc.capability, rc.permission
1763               FROM {context} ctx
1764               JOIN {role_capabilities} rc
1765                    ON rc.contextid=ctx.id
1766              WHERE (ctx.id IN ($contexts)
1767                     OR ctx.path LIKE ?)
1768                    AND rc.roleid = ?
1769           ORDER BY ctx.depth, ctx.path";
1770     $params = array($context->path."/%", $roleid);
1771     if ($rs = $DB->get_recordset_sql($sql, $params)) {
1772         foreach ($rs as $rd) {
1773             $k = "{$rd->path}:{$roleid}";
1774             $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1775         }
1776         $rs->close();
1777     }
1779     //
1780     // Say we loaded everything for the course context
1781     // - which we just did - if the user gets a proper
1782     // RA in this session, this data will need to be reloaded,
1783     // but that is handled by the complete accessdata reload
1784     //
1785     array_push($accessdata['loaded'], $context->path);
1787     //
1788     // Add the ghost RA
1789     //
1790     if (isset($accessdata['ra'][$context->path])) {
1791         array_push($accessdata['ra'][$context->path], $roleid);
1792     } else {
1793         $accessdata['ra'][$context->path] = array($roleid);
1794     }
1796     return $accessdata;
1800 /**
1801  * Check all the login enrolment information for the given user object
1802  * by querying the enrolment plugins
1803  *
1804  * @global object
1805  * @param object $user
1806  * @return void
1807  */
1808 function check_enrolment_plugins(&$user) {
1809     global $CFG;
1811     if (empty($user->id) or isguestuser($user)) {
1812         // shortcut - there is no enrolment work for guests and not-logged-in users
1813         return;
1814     }
1816     static $inprogress = array();  // To prevent this function being called more than once in an invocation
1818     if (!empty($inprogress[$user->id])) {
1819         return;
1820     }
1822     $inprogress[$user->id] = true;  // Set the flag
1824     require_once($CFG->dirroot .'/enrol/enrol.class.php');
1826     if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
1827         $plugins = array($CFG->enrol);
1828     }
1830     foreach ($plugins as $plugin) {
1831         $enrol = enrolment_factory::factory($plugin);
1832         if (method_exists($enrol, 'setup_enrolments')) {  /// Plugin supports Roles (Moodle 1.7 and later)
1833             $enrol->setup_enrolments($user);
1834         } else {                                          /// Run legacy enrolment methods
1835             if (method_exists($enrol, 'get_student_courses')) {
1836                 $enrol->get_student_courses($user);
1837             }
1838             if (method_exists($enrol, 'get_teacher_courses')) {
1839                 $enrol->get_teacher_courses($user);
1840             }
1842         /// deal with $user->students and $user->teachers stuff
1843             unset($user->student);
1844             unset($user->teacher);
1845         }
1846         unset($enrol);
1847     }
1849     unset($inprogress[$user->id]);  // Unset the flag
1852 /**
1853  * Returns array of all legacy roles.
1854  *
1855  * @return array
1856  */
1857 function get_legacy_roles() {
1858     return array(
1859         'admin'          => 'moodle/legacy:admin',
1860         'coursecreator'  => 'moodle/legacy:coursecreator',
1861         'editingteacher' => 'moodle/legacy:editingteacher',
1862         'teacher'        => 'moodle/legacy:teacher',
1863         'student'        => 'moodle/legacy:student',
1864         'guest'          => 'moodle/legacy:guest',
1865         'user'           => 'moodle/legacy:user'
1866     );
1869 /**
1870  * @param int roleid
1871  * @return string
1872  */
1873 function get_legacy_type($roleid) {
1874     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1875     $legacyroles = get_legacy_roles();
1877     $result = '';
1878     foreach($legacyroles as $ltype=>$lcap) {
1879         $localoverride = get_local_override($roleid, $sitecontext->id, $lcap);
1880         if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) {
1881             //choose first selected legacy capability - reset the rest
1882             if (empty($result)) {
1883                 $result = $ltype;
1884             } else {
1885                 unassign_capability($lcap, $roleid);
1886             }
1887         }
1888     }
1890     return $result;
1893 /**
1894  * Assign the defaults found in this capabality definition to roles that have
1895  * the corresponding legacy capabilities assigned to them.
1896  *
1897  * @param string $capability
1898  * @param array $legacyperms an array in the format (example):
1899  *                      'guest' => CAP_PREVENT,
1900  *                      'student' => CAP_ALLOW,
1901  *                      'teacher' => CAP_ALLOW,
1902  *                      'editingteacher' => CAP_ALLOW,
1903  *                      'coursecreator' => CAP_ALLOW,
1904  *                      'admin' => CAP_ALLOW
1905  * @return boolean success or failure.
1906  */
1907 function assign_legacy_capabilities($capability, $legacyperms) {
1909     $legacyroles = get_legacy_roles();
1911     foreach ($legacyperms as $type => $perm) {
1913         $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1915         if (!array_key_exists($type, $legacyroles)) {
1916             print_error('invalidlegacy', '', '', $type);
1917         }
1919         if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW)) {
1920             foreach ($roles as $role) {
1921                 // Assign a site level capability.
1922                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1923                     return false;
1924                 }
1925             }
1926         }
1927     }
1928     return true;
1932 /**
1933  * Checks to see if a capability is one of the special capabilities
1934  *
1935  * Checks to see if a capability is one of the special capabilities
1936  *      (either a legacy capability, or moodle/site:doanything).
1937  *
1938  * @param string $capabilityname the capability name, e.g. mod/forum:view.
1939  * @return boolean whether this is one of the special capabilities.
1940  */
1941 function is_legacy($capabilityname) {
1942     if ($capabilityname == 'moodle/site:doanything' || strpos($capabilityname, 'moodle/legacy') === 0) {
1943         return true;
1944     } else {
1945         return false;
1946     }
1949 /**
1950  * @param object $capability a capbility - a row from the capabilitites table.
1951  * @return boolean whether this capability is safe - that is, wether people with the
1952  *      safeoverrides capability should be allowed to change it.
1953  */
1954 function is_safe_capability($capability) {
1955     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1958 /**********************************
1959  * Context Manipulation functions *
1960  **********************************/
1962 /**
1963  * Create a new context record for use by all roles-related stuff
1964  *
1965  * Create a new context record for use by all roles-related stuff
1966  * assumes that the caller has done the homework.
1967  *
1968  * @global object
1969  * @global object
1970  * @param int $contextlevel
1971  * @param int $instanceid
1972  * @return object newly created context
1973  */
1974 function create_context($contextlevel, $instanceid) {
1976     global $CFG, $DB;
1978     if ($contextlevel == CONTEXT_SYSTEM) {
1979         return create_system_context();
1980     }
1982     $context = new object();
1983     $context->contextlevel = $contextlevel;
1984     $context->instanceid = $instanceid;
1986     // Define $context->path based on the parent
1987     // context. In other words... Who is your daddy?
1988     $basepath  = '/' . SYSCONTEXTID;
1989     $basedepth = 1;
1991     $result = true;
1992     $error_message = null;
1994     switch ($contextlevel) {
1995         case CONTEXT_COURSECAT:
1996             $sql = "SELECT ctx.path, ctx.depth
1997                       FROM {context}           ctx
1998                       JOIN {course_categories} cc
1999                            ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
2000                      WHERE cc.id=?";
2001             $params = array($instanceid);
2002             if ($p = $DB->get_record_sql($sql, $params)) {
2003                 $basepath  = $p->path;
2004                 $basedepth = $p->depth;
2005             } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid))) {
2006                 if (empty($category->parent)) {
2007                     // ok - this is a top category
2008                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
2009                     $basepath  = $parent->path;
2010                     $basedepth = $parent->depth;
2011                 } else {
2012                     // wrong parent category - no big deal, this can be fixed later
2013                     $basepath  = null;
2014                     $basedepth = 0;
2015                 }
2016             } else {
2017                 // incorrect category id
2018                 $error_message = "incorrect course category id ($instanceid)";
2019                 $result = false;
2020             }
2021             break;
2023         case CONTEXT_COURSE:
2024             $sql = "SELECT ctx.path, ctx.depth
2025                       FROM {context} ctx
2026                       JOIN {course}  c
2027                            ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
2028                      WHERE c.id=? AND c.id !=" . SITEID;
2029             $params = array($instanceid);
2030             if ($p = $DB->get_record_sql($sql, $params)) {
2031                 $basepath  = $p->path;
2032                 $basedepth = $p->depth;
2033             } else if ($course = $DB->get_record('course', array('id'=>$instanceid))) {
2034                 if ($course->id == SITEID) {
2035                     //ok - no parent category
2036                 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
2037                     $basepath  = $parent->path;
2038                     $basedepth = $parent->depth;
2039                 } else {
2040                     // wrong parent category of course - no big deal, this can be fixed later
2041                     $basepath  = null;
2042                     $basedepth = 0;
2043                 }
2044             } else if ($instanceid == SITEID) {
2045                 // no errors for missing site course during installation
2046                 return false;
2047             } else {
2048                 // incorrect course id
2049                 $error_message = "incorrect course id ($instanceid)";
2050                 $result = false;
2051             }
2052             break;
2054         case CONTEXT_MODULE:
2055             $sql = "SELECT ctx.path, ctx.depth
2056                       FROM {context}        ctx
2057                       JOIN {course_modules} cm
2058                            ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
2059                      WHERE cm.id=?";
2060             $params = array($instanceid);
2061             if ($p = $DB->get_record_sql($sql, $params)) {
2062                 $basepath  = $p->path;
2063                 $basedepth = $p->depth;
2064             } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid))) {
2065                 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course)) {
2066                     $basepath  = $parent->path;
2067                     $basedepth = $parent->depth;
2068                 } else {
2069                     // course does not exist - modules can not exist without a course
2070                     $error_message = "course does not exist ($cm->course) - modules can not exist without a course";
2071                     $result = false;
2072                 }
2073             } else {
2074                 // cm does not exist
2075                 $error_message = "cm with id $instanceid does not exist";
2076                 $result = false;
2077             }
2078             break;
2080         case CONTEXT_BLOCK:
2081             $sql = "SELECT ctx.path, ctx.depth
2082                       FROM {context} ctx
2083                       JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id)
2084                      WHERE bi.id = ?";
2085             $params = array($instanceid, CONTEXT_COURSE);
2086             if ($p = $DB->get_record_sql($sql, $params)) {
2087                 $basepath  = $p->path;
2088                 $basedepth = $p->depth;
2089             } else {
2090                 // block does not exist
2091                 $error_message = 'block or parent context does not exist';
2092                 $result = false;
2093             }
2094             break;
2095         case CONTEXT_USER:
2096             // default to basepath
2097             break;
2098     }
2100     // if grandparents unknown, maybe rebuild_context_path() will solve it later
2101     if ($basedepth != 0) {
2102         $context->depth = $basedepth+1;
2103     }
2105     if ($result and $id = $DB->insert_record('context', $context)) {
2106         // can't set the full path till we know the id!
2107         if ($basedepth != 0 and !empty($basepath)) {
2108             $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id));
2109         }
2110         return get_context_instance_by_id($id);
2112     } else {
2113         debugging('Error: could not insert new context level "'.
2114                   s($contextlevel).'", instance "'.
2115                   s($instanceid).'". ' . $error_message);
2117         return false;
2118     }
2121 /**
2122  * Returns system context or null if can not be created yet.
2123  *
2124  * @todo can not use get_record() because we do not know if query failed :-(
2125  * switch to get_record() later
2126  *
2127  * @global object
2128  * @global object
2129  * @param bool $cache use caching
2130  * @return mixed system context or null
2131  */
2132 function get_system_context($cache=true) {
2133     global $DB, $ACCESSLIB_PRIVATE;
2134     if ($cache and defined('SYSCONTEXTID')) {
2135         if (is_null($ACCESSLIB_PRIVATE->systemcontext)) {
2136             $ACCESSLIB_PRIVATE->systemcontext = new object();
2137             $ACCESSLIB_PRIVATE->systemcontext->id           = SYSCONTEXTID;
2138             $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM;
2139             $ACCESSLIB_PRIVATE->systemcontext->instanceid   = 0;
2140             $ACCESSLIB_PRIVATE->systemcontext->path         = '/'.SYSCONTEXTID;
2141             $ACCESSLIB_PRIVATE->systemcontext->depth        = 1;
2142         }
2143         return $ACCESSLIB_PRIVATE->systemcontext;
2144     }
2145     try {
2146         $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM));
2147     } catch (dml_exception $e) {
2148         //table does not exist yet, sorry
2149         return null;
2150     }
2152     if (!$context) {
2153         $context = new object();
2154         $context->contextlevel = CONTEXT_SYSTEM;
2155         $context->instanceid   = 0;
2156         $context->depth        = 1;
2157         $context->path         = NULL; //not known before insert
2159         try {
2160             $context->id = $DB->insert_record('context', $context);
2161         } catch (dml_exception $e) {
2162             // can not create context yet, sorry
2163             return null;
2164         }
2165     }
2167     if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) {
2168         $context->instanceid   = 0;
2169         $context->path         = '/'.$context->id;
2170         $context->depth        = 1;
2171         $DB->update_record('context', $context);
2172     }
2174     if (!defined('SYSCONTEXTID')) {
2175         define('SYSCONTEXTID', $context->id);
2176     }
2178     $ACCESSLIB_PRIVATE->systemcontext = $context;
2179     return $ACCESSLIB_PRIVATE->systemcontext;
2182 /**
2183  * Remove a context record and any dependent entries,
2184  * removes context from static context cache too
2185  *
2186  * @global object
2187  * @global object
2188  * @param int $level
2189  * @param int $instanceid
2190  * @return bool properly deleted
2191  */
2192 function delete_context($contextlevel, $instanceid) {
2193     global $DB, $ACCESSLIB_PRIVATE, $CFG;
2195     // do not use get_context_instance(), because the related object might not exist,
2196     // or the context does not exist yet and it would be created now
2197     if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
2198         $result = $DB->delete_records('role_assignments', array('contextid'=>$context->id)) &&
2199                   $DB->delete_records('role_capabilities', array('contextid'=>$context->id)) &&
2200                   $DB->delete_records('context', array('id'=>$context->id)) &&
2201                   $DB->delete_records('role_names', array('contextid'=>$context->id));
2203         // do not mark dirty contexts if parents unknown
2204         if (!is_null($context->path) and $context->depth > 0) {
2205             mark_context_dirty($context->path);
2206         }
2208         // purge static context cache if entry present
2209         unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]);
2210         unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]);
2212         blocks_delete_all_for_context($context->id);
2213         filter_delete_all_for_context($context->id);
2215         return $result;
2216     } else {
2218         return true;
2219     }
2222 /**
2223  * Precreates all contexts including all parents
2224  *
2225  * @global object
2226  * @param int $contextlevel empty means all
2227  * @param bool $buildpaths update paths and depths
2228  * @return void
2229  */
2230 function create_contexts($contextlevel=null, $buildpaths=true) {
2231     global $DB;
2233     //make sure system context exists
2234     $syscontext = get_system_context(false);
2236     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT
2237                              or $contextlevel == CONTEXT_COURSE
2238                              or $contextlevel == CONTEXT_MODULE
2239                              or $contextlevel == CONTEXT_BLOCK) {
2240         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2241                 SELECT ".CONTEXT_COURSECAT.", cc.id
2242                   FROM {course}_categories cc
2243                  WHERE NOT EXISTS (SELECT 'x'
2244                                      FROM {context} cx
2245                                     WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
2246         $DB->execute($sql);
2248     }
2250     if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE
2251                              or $contextlevel == CONTEXT_MODULE
2252                              or $contextlevel == CONTEXT_BLOCK) {
2253         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2254                 SELECT ".CONTEXT_COURSE.", c.id
2255                   FROM {course} c
2256                  WHERE NOT EXISTS (SELECT 'x'
2257                                      FROM {context} cx
2258                                     WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
2259         $DB->execute($sql);
2261     }
2263     if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE
2264                              or $contextlevel == CONTEXT_BLOCK) {
2265         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2266                 SELECT ".CONTEXT_MODULE.", cm.id
2267                   FROM {course}_modules cm
2268                  WHERE NOT EXISTS (SELECT 'x'
2269                                      FROM {context} cx
2270                                     WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
2271         $DB->execute($sql);
2272     }
2274     if (empty($contextlevel) or $contextlevel == CONTEXT_USER
2275                              or $contextlevel == CONTEXT_BLOCK) {
2276         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2277                 SELECT ".CONTEXT_USER.", u.id
2278                   FROM {user} u
2279                  WHERE u.deleted=0
2280                    AND NOT EXISTS (SELECT 'x'
2281                                      FROM {context} cx
2282                                     WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
2283         $DB->execute($sql);
2285     }
2287     if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) {
2288         $sql = "INSERT INTO {context} (contextlevel, instanceid)
2289                 SELECT ".CONTEXT_BLOCK.", bi.id
2290                   FROM {block_instances} bi
2291                  WHERE NOT EXISTS (SELECT 'x'
2292                                      FROM {context} cx
2293                                     WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
2294         $DB->execute($sql);
2295     }
2297     if ($buildpaths) {
2298         build_context_path(false);
2299     }
2302 /**
2303  * Remove stale context records
2304  *
2305  * @global object
2306  * @return bool
2307  */
2308 function cleanup_contexts() {
2309     global $DB;
2311     $sql = "  SELECT c.contextlevel,
2312                      c.instanceid AS instanceid
2313                 FROM {context} c
2314                 LEFT OUTER JOIN {course}_categories t
2315                      ON c.instanceid = t.id
2316                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
2317             UNION
2318               SELECT c.contextlevel,
2319                      c.instanceid
2320                 FROM {context} c
2321                 LEFT OUTER JOIN {course} t
2322                      ON c.instanceid = t.id
2323                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
2324             UNION
2325               SELECT c.contextlevel,
2326                      c.instanceid
2327                 FROM {context} c
2328                 LEFT OUTER JOIN {course}_modules t
2329                      ON c.instanceid = t.id
2330                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
2331             UNION
2332               SELECT c.contextlevel,
2333                      c.instanceid
2334                 FROM {context} c
2335                 LEFT OUTER JOIN {user} t
2336                      ON c.instanceid = t.id
2337                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
2338             UNION
2339               SELECT c.contextlevel,
2340                      c.instanceid
2341                 FROM {context} c
2342                 LEFT OUTER JOIN {block_instances} t
2343                      ON c.instanceid = t.id
2344                WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
2345            ";
2347     // transactions used only for performance reasons here
2348     $transaction = $DB->start_delegated_transaction();
2350     if ($rs = $DB->get_recordset_sql($sql)) {
2351         foreach ($rs as $ctx) {
2352             delete_context($ctx->contextlevel, $ctx->instanceid);
2353         }
2354         $rs->close();
2355     }
2357     $transaction->allow_commit();
2358     return true;
2361 /**
2362  * Preloads all contexts relating to a course: course, modules. Block contexts
2363  * are no longer loaded here. The contexts for all the blocks on the current
2364  * page are now efficiently loaded by {@link block_manager::load_blocks()}.
2365  *
2366  * @param int $courseid Course ID
2367  * @return void
2368  */
2369 function preload_course_contexts($courseid) {
2370     global $DB, $ACCESSLIB_PRIVATE;
2372     // Users can call this multiple times without doing any harm
2373     global $ACCESSLIB_PRIVATE;
2374     if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) {
2375         return;
2376     }
2378     $params = array($courseid, $courseid, $courseid);
2379     $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2380               FROM {course_modules} cm
2381               JOIN {context} x ON x.instanceid=cm.id
2382              WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE."
2384          UNION ALL
2386             SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth
2387               FROM {context} x
2388              WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE."";
2390     $rs = $DB->get_recordset_sql($sql, $params);
2391     foreach($rs as $context) {
2392         cache_context($context);
2393     }
2394     $rs->close();
2395     $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true;
2398 /**
2399  * Get the context instance as an object. This function will create the
2400  * context instance if it does not exist yet.
2401  *
2402  * @todo Remove code branch from previous fix MDL-9016 which is no longer needed
2403  *
2404  * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
2405  * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
2406  *      for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
2407  * @return object The context object.
2408  */
2409 function get_context_instance($contextlevel, $instance=0) {
2411     global $DB, $ACCESSLIB_PRIVATE;
2412     static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK);
2414     if ($contextlevel === 'clearcache') {
2415         // TODO: Remove for v2.0
2416         // No longer needed, but we'll catch it to avoid erroring out on custom code.
2417         // This used to be a fix for MDL-9016
2418         // "Restoring into existing course, deleting first
2419         //  deletes context and doesn't recreate it"
2420         return false;
2421     }
2423 /// System context has special cache
2424     if ($contextlevel == CONTEXT_SYSTEM) {
2425         return get_system_context();
2426     }
2428 /// check allowed context levels
2429     if (!in_array($contextlevel, $allowed_contexts)) {
2430         // fatal error, code must be fixed - probably typo or switched parameters
2431         print_error('invalidcourselevel');
2432     }
2434     if (!is_array($instance)) {
2435     /// Check the cache
2436         if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) {  // Already cached
2437             return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
2438         }
2440     /// Get it from the database, or create it
2441         if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) {
2442             $context = create_context($contextlevel, $instance);
2443         }
2445     /// Only add to cache if context isn't empty.
2446         if (!empty($context)) {
2447             cache_context($context);
2448         }
2450         return $context;
2451     }
2454 /// ok, somebody wants to load several contexts to save some db queries ;-)
2455     $instances = $instance;
2456     $result = array();
2458     foreach ($instances as $key=>$instance) {
2459     /// Check the cache first
2460         if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) {  // Already cached
2461             $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance];
2462             unset($instances[$key]);
2463             continue;
2464         }
2465     }
2467     if ($instances) {
2468         list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM);
2469         array_unshift($params, $contextlevel);
2470         $sql = "SELECT instanceid, id, contextlevel, path, depth
2471                   FROM {context}
2472                  WHERE contextlevel=? AND instanceid $instanceids";
2474         if (!$contexts = $DB->get_records_sql($sql, $params)) {
2475             $contexts = array();
2476         }
2478         foreach ($instances as $instance) {
2479             if (isset($contexts[$instance])) {
2480                 $context = $contexts[$instance];
2481             } else {
2482                 $context = create_context($contextlevel, $instance);
2483             }
2485             if (!empty($context)) {
2486                 cache_context($context);
2487             }
2489             $result[$instance] = $context;
2490         }
2491     }
2493     return $result;
2497 /**
2498  * Get a context instance as an object, from a given context id.
2499  *
2500  * @param mixed $id a context id or array of ids.
2501  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
2502  *                        MUST_EXIST means throw exception if no record or multiple records found
2503  * @return mixed object, array of the context object, or false.
2504  */
2505 function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) {
2506     global $DB, $ACCESSLIB_PRIVATE;
2508     if ($id == SYSCONTEXTID) {
2509         return get_system_context();
2510     }
2512     if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) {  // Already cached
2513         return $ACCESSLIB_PRIVATE->contextsbyid[$id];
2514     }
2516     if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
2517         cache_context($context);
2518         return $context;
2519     }
2521     return false;
2525 /**
2526  * Get the local override (if any) for a given capability in a role in a context
2527  *
2528  * @global object
2529  * @param int $roleid
2530  * @param int $contextid
2531  * @param string $capability
2532  */
2533 function get_local_override($roleid, $contextid, $capability) {
2534     global $DB;
2535     return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
2538 /**
2539  * Returns context instance plus related course and cm instances
2540  * @param int $contextid
2541  * @return array of ($context, $course, $cm)
2542  */
2543 function get_context_info_array($contextid) {
2544     global $DB;
2546     $context = get_context_instance_by_id($contextid, MUST_EXIST);
2547     $course  = null;
2548     $cm      = null;
2550     if ($context->contextlevel == CONTEXT_COURSE) {
2551         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
2553     } else if ($context->contextlevel == CONTEXT_MODULE) {
2554         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2555         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2557     } else if ($context->contextlevel == CONTEXT_BLOCK) {
2558         $parentcontexts = get_parent_contexts($context, false);
2559         $parent = reset($parentcontexts);
2560         $parent = get_context_instance_by_id($parent);
2562         if ($parent->contextlevel == CONTEXT_COURSE) {
2563             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
2564         } else if ($parent->contextlevel == CONTEXT_MODULE) {
2565             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
2566             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
2567         }
2568     }
2570     return array($context, $course, $cm);
2574 //////////////////////////////////////
2575 //    DB TABLE RELATED FUNCTIONS    //
2576 //////////////////////////////////////
2578 /**
2579  * function that creates a role
2580  *
2581  * @global object
2582  * @param string $name role name
2583  * @param string $shortname role short name
2584  * @param string $description role description
2585  * @param string $legacy optional legacy capability
2586  * @return mixed id or dml_exception
2587  */
2588 function create_role($name, $shortname, $description, $legacy='') {
2589     global $DB;
2591     // Get the system context.
2592     $context = get_context_instance(CONTEXT_SYSTEM);
2594     // Insert the role record.
2595     $role = new object();
2596     $role->name        = $name;
2597     $role->shortname   = $shortname;
2598     $role->description = $description;
2600     //find free sortorder number
2601     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
2602     if (empty($role->sortorder)) {
2603         $role->sortorder = 1;
2604     }
2605     $id = $DB->insert_record('role', $role);
2607     if ($legacy) {
2608         assign_capability($legacy, CAP_ALLOW, $id, $context->id);
2609     }
2611     return $id;
2614 /**
2615  * Function that deletes a role and cleanups up after it
2616  *
2617  * @global object
2618  * @global object
2619  * @param int $roleid id of role to delete
2620  * @return bool
2621  */
2622 function delete_role($roleid) {
2623     global $CFG, $DB;
2624     $success = true;
2626 // mdl 10149, check if this is the last active admin role
2627 // if we make the admin role not deletable then this part can go
2629     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
2631     if ($role = $DB->get_record('role', array('id'=>$roleid))) {
2632         if ($DB->record_exists('role_capabilities', array('contextid'=>$systemcontext->id, 'roleid'=>$roleid, 'capability'=>'moodle/site:doanything'))) {
2633             // deleting an admin role
2634             $status = false;
2635             if ($adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW, $systemcontext)) {
2636                 foreach ($adminroles as $adminrole) {
2637                     if ($adminrole->id != $roleid) {
2638                         // some other admin role
2639                         if ($DB->record_exists('role_assignments', array('roleid'=>$adminrole->id, 'contextid'=>$systemcontext->id))) {
2640                             // found another admin role with at least 1 user assigned
2641                             $status = true;
2642                             break;
2643                         }
2644                     }
2645                 }
2646             }
2647             if ($status !== true) {
2648                 print_error('cannotdeleterolenoadmin', 'access');
2649             }
2650         }
2651     }
2653 // first unssign all users
2654     if (!role_unassign($roleid)) {
2655         debugging("Error while unassigning all users from role with ID $roleid!");
2656         $success = false;
2657     }
2659 // cleanup all references to this role, ignore errors
2660     if ($success) {
2661         $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
2662         $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
2663         $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
2664         $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
2665         $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
2666         $DB->delete_records('role_names',          array('roleid'=>$roleid));
2667         $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
2668     }
2670 // finally delete the role itself
2671     // get this before the name is gone for logging
2672     $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
2674     if ($success and !$DB->delete_records('role', array('id'=>$roleid))) {
2675         debugging("Could not delete role record with ID $roleid!");
2676         $success = false;
2677     }
2679     if ($success) {
2680         add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
2681     }
2683     return $success;
2686 /**
2687  * Function to write context specific overrides, or default capabilities.
2688  *
2689  * @global object
2690  * @global object
2691  * @param string module string name
2692  * @param string capability string name
2693  * @param int contextid context id
2694  * @param int roleid role id
2695  * @param int permission int 1,-1 or -1000 should not be writing if permission is 0
2696  * @return bool
2697  */
2698 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) {
2700     global $USER, $DB;
2702     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
2703         unassign_capability($capability, $roleid, $contextid);
2704         return true;
2705     }
2707     $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability));
2709     if ($existing and !$overwrite) {   // We want to keep whatever is there already
2710         return true;
2711     }
2713     $cap = new object;
2714     $cap->contextid = $contextid;
2715     $cap->roleid = $roleid;
2716     $cap->capability = $capability;
2717     $cap->permission = $permission;
2718     $cap->timemodified = time();
2719     $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
2721     if ($existing) {
2722         $cap->id = $existing->id;
2723         return $DB->update_record('role_capabilities', $cap);
2724     } else {
2725         $c = $DB->get_record('context', array('id'=>$contextid));
2726         return $DB->insert_record('role_capabilities', $cap);
2727     }
2730 /**
2731  * Unassign a capability from a role.
2732  *
2733  * @global object
2734  * @param int $roleid the role id
2735  * @param string $capability the name of the capability
2736  * @return boolean success or failure
2737  */
2738 function unassign_capability($capability, $roleid, $contextid=NULL) {
2739     global $DB;
2741     if (isset($contextid)) {
2742         // delete from context rel, if this is the last override in this context
2743         $status = $DB->delete_records('role_capabilities', array('capability'=>$capability,
2744                 'roleid'=>$roleid, 'contextid'=>$contextid));
2745     } else {
2746         $status = $DB->delete_records('role_capabilities', array('capability'=>$capability,
2747                 'roleid'=>$roleid));
2748     }
2749     return $status;
2753 /**
2754  * Get the roles that have a given capability assigned to it
2755  * Get the roles that have a given capability assigned to it. This function
2756  * does not resolve the actual permission of the capability. It just checks
2757  * for assignment only.
2758  *
2759  * @global object
2760  * @global object
2761  * @param string $capability - capability name (string)
2762  * @param null $permission - optional, the permission defined for this capability
2763  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL
2764  * @param object $contect
2765  * @return mixed array or role objects
2766  */
2767 function get_roles_with_capability($capability, $permission=NULL, $context='') {
2769     global $CFG, $DB;
2771     $params = array();
2773     if ($context) {
2774         if ($contexts = get_parent_contexts($context)) {
2775             $listofcontexts = '('.implode(',', $contexts).')';
2776         } else {
2777             $sitecontext = get_context_instance(CONTEXT_SYSTEM);
2778             $listofcontexts = '('.$sitecontext->id.')'; // must be site
2779         }
2780         $contextstr = "AND (rc.contextid = ? OR  rc.contextid IN $listofcontexts)";
2781         $params[] = $context->id;
2782     } else {
2783         $contextstr = '';
2784     }
2786     $selectroles = "SELECT r.*
2787                       FROM {role} r,
2788                            {role_capabilities} rc
2789                      WHERE rc.capability = ?
2790                            AND rc.roleid = r.id $contextstr";
2792     array_unshift($params, $capability);
2794     if (isset($permission)) {
2795         $selectroles .= " AND rc.permission = ?";
2796         $params[] = $permission;
2797     }
2798     return $DB->get_records_sql($selectroles, $params);
2802 /**
2803  * This function makes a role-assignment (a role for a user or group in a particular context)
2804  *
2805  * @global object
2806  * @global object
2807  * @global object
2808  * @param int $roleid the role of the id
2809  * @param int $userid userid
2810  * @param int $groupid group id
2811  * @param int $contextid id of the context
2812  * @param int $timestart time this assignment becomes effective defaults to 0
2813  * @param int $timeend time this assignemnt ceases to be effective defaults to 0
2814  * @param int $hidden defaults to 0
2815  * @param string $enrol defaults to 'manual'
2816  * @param string $timemodified defaults to ''
2817  * @return int new id of the assigment
2818  */
2819 function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden=0, $enrol='manual',$timemodified='') {
2820     global $USER, $CFG, $DB;
2822 /// Do some data validation
2824     if (empty($roleid)) {
2825         debugging('Role ID not provided');
2826         return false;
2827     }
2829     if (empty($userid) && empty($groupid)) {
2830         debugging('Either userid or groupid must be provided');
2831         return false;
2832     }
2834     if ($userid && !$DB->record_exists('user', array('id'=>$userid))) {
2835         debugging('User ID '.intval($userid).' does not exist!');
2836         return false;
2837     }
2839     if ($groupid && !groups_group_exists($groupid)) {
2840         debugging('Group ID '.intval($groupid).' does not exist!');
2841         return false;
2842     }
2844     if (!$context = get_context_instance_by_id($contextid)) {
2845         debugging('Context ID '.intval($contextid).' does not exist!');
2846         return false;
2847     }
2849     if (($timestart and $timeend) and ($timestart > $timeend)) {
2850         debugging('The end time can not be earlier than the start time');
2851         return false;
2852     }
2854     if (!$timemodified) {
2855         $timemodified = time();
2856     }
2858 /// Check for existing entry
2859     if ($userid) {
2860         $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid));
2861     } else {
2862         $ra = $DB->get_record('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'groupid'=>$groupid));
2863     }
2865     if (empty($ra)) {             // Create a new entry
2866         $ra = new object();
2867         $ra->roleid = $roleid;
2868         $ra->contextid = $context->id;
2869         $ra->userid = $userid;
2870         $ra->hidden = $hidden;
2871         $ra->enrol = $enrol;
2872     /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2873     /// by repeating queries with the same exact parameters in a 100 secs time window
2874         $ra->timestart = round($timestart, -2);
2875         $ra->timeend = $timeend;
2876         $ra->timemodified = $timemodified;
2877         $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
2879         $ra->id = $DB->insert_record('role_assignments', $ra);
2881     } else {                      // We already have one, just update it
2882         $ra->id = $ra->id;
2883         $ra->hidden = $hidden;
2884         $ra->enrol = $enrol;
2885     /// Always round timestart downto 100 secs to help DBs to use their own caching algorithms
2886     /// by repeating queries with the same exact parameters in a 100 secs time window
2887         $ra->timestart = round($timestart, -2);
2888         $ra->timeend = $timeend;
2889         $ra->timemodified = $timemodified;
2890         $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
2892         $DB->update_record('role_assignments', $ra);
2893     }
2895 /// mark context as dirty - modules might use has_capability() in xxx_role_assing()
2896 /// again expensive, but needed
2897     mark_context_dirty($context->path);
2899     if (!empty($USER->id) && $USER->id == $userid) {
2900 /// If the user is the current user, then do full reload of capabilities too.
2901         load_all_capabilities();
2902     }
2904 /// Ask all the modules if anything needs to be done for this user
2905     $mods = get_plugin_list('mod');
2906     foreach ($mods as $mod => $moddir) {
2907         include_once($moddir.'/lib.php');
2908         $functionname = $mod.'_role_assign';
2909         if (function_exists($functionname)) {
2910             $functionname($userid, $context, $roleid);
2911         }
2912     }
2914     /// now handle metacourse role assignments if in course context
2915     if ($context->contextlevel == CONTEXT_COURSE) {
2916         if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
2917             foreach ($parents as $parent) {
2918                 sync_metacourse($parent->parent_course);
2919             }
2920         }
2921     }
2923     events_trigger('role_assigned', $ra);
2925     return $ra->id;
2929 /**
2930  * Deletes one or more role assignments.   You must specify at least one parameter.
2931  *
2932  * @global object
2933  * @global object
2934  * @global object
2935  * @param int $roleid defaults to 0
2936  * @param int $userid defaults to 0
2937  * @param int $groupid defaults to 0
2938  * @param int $contextid defaults to 0
2939  * @param mixed $enrol unassign only if enrolment type matches, NULL means anything. Defaults to NULL
2940  * @return boolean success or failure
2941  */
2942 function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NULL) {
2944     global $USER, $CFG, $DB;
2945     require_once($CFG->dirroot.'/group/lib.php');
2947     $success = true;
2949     $args = array('roleid', 'userid', 'groupid', 'contextid');
2950     $select = array();
2951     $params = array();
2953     foreach ($args as $arg) {
2954         if ($$arg) {
2955             $select[] = "$arg = ?";
2956             $params[] = $$arg;
2957         }
2958     }
2959     if (!empty($enrol)) {
2960         $select[] = "enrol=?";
2961         $params[] = $enrol;
2962     }
2964     if ($select) {
2965         if ($ras = $DB->get_records_select('role_assignments', implode(' AND ', $select), $params)) {
2966             $mods = get_plugin_list('mod');
2967             foreach($ras as $ra) {
2968                 $fireevent = false;
2969                 /// infinite loop protection when deleting recursively
2970                 if (!$ra = $DB->get_record('role_assignments', array('id'=>$ra->id))) {
2971                     continue;
2972                 }
2973                 if ($DB->delete_records('role_assignments', array('id'=>$ra->id))) {
2974                     $fireevent = true;
2975                 } else {
2976                     $success = false;
2977                 }
2979                 if (!$context = get_context_instance_by_id($ra->contextid)) {
2980                     // strange error, not much to do
2981                     continue;
2982                 }
2984                 /* mark contexts as dirty here, because we need the refreshed
2985                  * caps bellow to delete group membership and user_lastaccess!
2986                  * and yes, this is very expensive for bulk operations :-(
2987                  */
2988                 mark_context_dirty($context->path);
2990                 /// If the user is the current user, then do full reload of capabilities too.
2991                 if (!empty($USER->id) && $USER->id == $ra->userid) {
2992                     load_all_capabilities();
2993                 }
2995                 /// Ask all the modules if anything needs to be done for this user
2996                 foreach ($mods as $mod=>$moddir) {
2997                     include_once($moddir.'/lib.php');
2998                     $functionname = $mod.'_role_unassign';
2999                     if (function_exists($functionname)) {
3000                         $functionname($ra->userid, $context); // watch out, $context might be NULL if something goes wrong
3001                     }
3002                 }
3004                 /// now handle metacourse role unassigment and removing from goups if in course context
3005                 if ($context->contextlevel == CONTEXT_COURSE) {
3007                     // cleanup leftover course groups/subscriptions etc when user has
3008                     // no capability to view course
3009                     // this may be slow, but this is the proper way of doing it
3010                     if (!has_capability('moodle/course:view', $context, $ra->userid)) {
3011                         // remove from groups
3012                         groups_delete_group_members($context->instanceid, $ra->userid);
3014                         // delete lastaccess records
3015                         $DB->delete_records('user_lastaccess', array('userid'=>$ra->userid, 'courseid'=>$context->instanceid));
3016                     }
3018                     //unassign roles in metacourses if needed
3019                     if ($parents = $DB->get_records('course_meta', array('child_course'=>$context->instanceid))) {
3020                         foreach ($parents as $parent) {
3021                             sync_metacourse($parent->parent_course);
3022                         }
3023                     }
3024                 }
3026                 if ($fireevent) {
3027                     events_trigger('role_unassigned', $ra);
3028                 }
3029             }
3030         }
3031     }
3033     return $success;
3036 /**
3037  * Enrol someone without using the default role in a course
3038  *
3039  * A convenience function to take care of the common case where you
3040  * just want to enrol someone using the default role into a course
3041  *
3042  * @param object $course
3043  * @param object $user
3044  * @param string $enrol the plugin used to do this enrolment
3045  * @return bool
3046  */
3047 function enrol_into_course($course, $user, $enrol) {
3049     $timestart = time();
3050     // remove time part from the timestamp and keep only the date part
3051     $timestart = make_timestamp(date('Y', $timestart), date('m', $timestart), date('d', $timestart), 0, 0, 0);
3052     if ($course->enrolperiod) {
3053         $timeend = $timestart + $course->enrolperiod;
3054     } else {
3055         $timeend = 0;
3056     }
3058     if ($role = get_default_course_role($course)) {
3060         $context = get_context_instance(CONTEXT_COURSE, $course->id);
3062         if (!role_assign($role->id, $user->id, 0, $context->id, $timestart, $timeend, 0, $enrol)) {
3063             return false;
3064         }
3066         // force accessdata refresh for users visiting this context...
3067         mark_context_dirty($context->path);
3069         email_welcome_message_to_user($course, $user);
3071         add_to_log($course->id, 'course', 'enrol',
3072                 'view.php?id='.$course->id, $course->id);
3074         return true;
3075     }
3077     return false;
3080 /**
3081  * Loads the capability definitions for the component (from file).
3082  *
3083  * Loads the capability definitions for the component (from file). If no
3084  * capabilities are defined for the component, we simply return an empty array.
3085  *
3086  * @global object
3087  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
3088  * @return array array of capabilities
3089  */
3090 function load_capability_def($component) {
3091     $defpath = get_component_directory($component).'/db/access.php';
3093     $capabilities = array();
3094     if (file_exists($defpath)) {
3095         require($defpath);
3096         if (!empty(${$component.'_capabilities'})) {
3097             // legacy capability array name
3098             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
3099             $capabilities = ${$component.'_capabilities'};
3100         }
3101     }
3103     return $capabilities;
3107 /**
3108  * Gets the capabilities that have been cached in the database for this component.
3109  * @param string $component - examples: 'moodle', 'mod_forum'
3110  * @return array array of capabilities
3111  */
3112 function get_cached_capabilities($component='moodle') {
3113     global $DB;
3114     return $DB->get_records('capabilities', array('component'=>$component));
3117 /**
3118  * Returns default capabilities for given legacy role type.
3119  * @param string $legacyrole legacy role name
3120  * @return array
3121  */
3122 function get_default_capabilities($legacyrole) {
3123     global $DB;
3124     $allcaps = $DB->get_records('capabilities');
3125     $alldefs = array();
3126     $defaults = array();
3127     $components = array();
3128     foreach ($allcaps as $cap) {
3129         if (!in_array($cap->component, $components)) {
3130             $components[] = $cap->component;
3131             $alldefs = array_merge($alldefs, load_capability_def($cap->component));
3132         }
3133     }
3134     foreach($alldefs as $name=>$def) {
3135         if (isset($def['legacy'][$legacyrole])) {
3136             $defaults[$name] = $def['legacy'][$legacyrole];
3137         }
3138     }
3140     //some exceptions
3141     $defaults['moodle/legacy:'.$legacyrole] = CAP_ALLOW;
3142     if ($legacyrole == 'admin') {
3143         $defaults['moodle/site:doanything'] = CAP_ALLOW;
3144     }
3145     return $defaults;
3148 /**
3149  * Reset role capabilitites to default according to selected legacy capability.
3150  * If several legacy caps selected, use the first from get_default_capabilities.
3151  * If no legacy selected, removes all capabilities.
3152  * @param int @roleid
3153  */
3154 function reset_role_capabilities($roleid) {
3155     global $DB;
3157     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
3158     $legacyroles = get_legacy_roles();
3160     $defaultcaps = array();
3161     foreach($legacyroles as $ltype=>$lcap) {
3162         $localoverride = get_local_override($roleid, $sitecontext->id, $lcap);
3163         if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) {
3164             //choose first selected legacy capability
3165             $defaultcaps = get_default_capabilities($ltype);
3166             break;
3167         }
3168     }
3170     $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
3171     if (!empty($defaultcaps)) {
3172         foreach($defaultcaps as $cap=>$permission) {
3173             assign_capability($cap, $permission, $roleid, $sitecontext->id);
3174         }
3175     }
3178 /**
3179  * Updates the capabilities table with the component capability definitions.
3180  * If no parameters are given, the function updates the core moodle
3181  * capabilities.
3182  *
3183  * Note that the absence of the db/access.php capabilities definition file
3184  * will cause any stored capabilities for the component to be removed from
3185  * the database.
3186  *
3187  * @global object
3188  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
3189  * @return boolean true if success, exception in case of any problems
3190  */
3191 function update_capabilities($component='moodle') {
3192     global $DB, $OUTPUT;
3194     $storedcaps = array();
3196     $filecaps = load_capability_def($component);
3197     $cachedcaps = get_cached_capabilities($component);
3198     if ($cachedcaps) {
3199         foreach ($cachedcaps as $cachedcap) {
3200             array_push($storedcaps, $cachedcap->name);
3201             // update risk bitmasks and context levels in existing capabilities if needed
3202             if (array_key_exists($cachedcap->name, $filecaps)) {
3203                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
3204                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
3205                 }
3206                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
3207                     $updatecap = new object();
3208                     $updatecap->id = $cachedcap->id;
3209                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
3210                     $DB->update_record('capabilities', $updatecap);
3211                 }
3213                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
3214                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
3215                 }
3216                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
3217                     $updatecap = new object();
3218                     $updatecap->id = $cachedcap->id;
3219                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
3220                     $DB->update_record('capabilities', $updatecap);
3221                 }
3222             }
3223         }
3224     }
3226     // Are there new capabilities in the file definition?
3227     $newcaps = array();
3229     foreach ($filecaps as $filecap => $def) {
3230         if (!$storedcaps ||
3231                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
3232             if (!array_key_exists('riskbitmask', $def)) {
3233                 $def['riskbitmask'] = 0; // no risk if not specified
3234             }
3235             $newcaps[$filecap] = $def;
3236         }
3237     }
3238     // Add new capabilities to the stored definition.
3239     foreach ($newcaps as $capname => $capdef) {
3240         $capability = new object;
3241         $capability->name = $capname;
3242         $capability->captype = $capdef['captype'];
3243         $capability->contextlevel = $capdef['contextlevel'];
3244         $capability->component = $component;
3245         $capability->riskbitmask = $capdef['riskbitmask'];
3247         $DB->insert_record('capabilities', $capability, false);
3249         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){
3250             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
3251                 foreach ($rolecapabilities as $rolecapability){
3252                     //assign_capability will update rather than insert if capability exists
3253                     if (!assign_capability($capname, $rolecapability->permission,
3254                                             $rolecapability->roleid, $rolecapability->contextid, true)){
3255                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
3256                     }
3257                 }
3258             }
3259         // Do we need to assign the new capabilities to roles that have the
3260         // legacy capabilities moodle/legacy:* as well?
3261         // we ignore legacy key if we have cloned permissions
3262         } else if (isset($capdef['legacy']) && is_array($capdef['legacy']) &&
3263                     !assign_legacy_capabilities($capname, $capdef['legacy'])) {
3264             echo $OUTPUT->notification('Could not assign legacy capabilities for '.$capname);
3265         }
3266     }
3267     // Are there any capabilities that have been removed from the file
3268     // definition that we need to delete from the stored capabilities and
3269     // role assignments?
3270     capabilities_cleanup($component, $filecaps);
3272     // reset static caches
3273     is_valid_capability('reset', false);
3275     return true;
3279 /**
3280  * Deletes cached capabilities that are no longer needed by the component.
3281  * Also unassigns these capabilities from any roles that have them.
3282  *
3283  * @global object
3284  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
3285  * @param array $newcapdef array of the new capability definitions that will be
3286  *                     compared with the cached capabilities
3287  * @return int number of deprecated capabilities that have been removed
3288  */
3289 function capabilities_cleanup($component, $newcapdef=NULL) {
3290     global $DB;
3292     $removedcount = 0;
3294     if ($cachedcaps = get_cached_capabilities($component)) {
3295         foreach ($cachedcaps as $cachedcap) {
3296             if (empty($newcapdef) ||
3297                         array_key_exists($cachedcap->name, $newcapdef) === false) {
3299                 // Remove from capabilities cache.
3300                 $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
3301                 $removedcount++;
3302                 // Delete from roles.
3303                 if ($roles = get_roles_with_capability($cachedcap->name)) {
3304                     foreach($roles as $role) {
3305                         if (!unassign_capability($cachedcap->name, $role->id)) {
3306                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
3307                         }
3308                     }
3309                 }
3310             } // End if.
3311         }
3312     }
3313     return $removedcount;
3318 //////////////////
3319 // UI FUNCTIONS //
3320 //////////////////
3322 /**
3323  * @param integer $contextlevel $context->context level. One of the CONTEXT_... constants.
3324  * @return string the name for this type of context.
3325  */
3326 function get_contextlevel_name($contextlevel) {
3327     static $strcontextlevels = null;
3328     if (is_null($strcontextlevels)) {
3329         $strcontextlevels = array(
3330             CONTEXT_SYSTEM => get_string('coresystem'),
3331             CONTEXT_USER => get_string('user'),
3332             CONTEXT_COURSECAT => get_string('category'),
3333             CONTEXT_COURSE => get_string('course'),
3334             CONTEXT_MODULE => get_string('activitymodule'),
3335             CONTEXT_BLOCK => get_string('block')
3336         );
3337     }
3338     return $strcontextlevels[$contextlevel];
3341 /**
3342  * Prints human readable context identifier.
3343  *
3344  * @global object
3345  * @param object $context the context.
3346  * @param boolean $withprefix whether to prefix the name of the context with the
3347  *      type of context, e.g. User, Course, Forum, etc.
3348  * @param boolean $short whether to user the short name of the thing. Only applies
3349  *      to course contexts
3350  * @return string the human readable context name.
3351  */
3352 function print_context_name($context, $withprefix = true, $short = false) {
3353     global $DB;
3355     $name = '';
3356     switch ($context->contextlevel) {
3358         case CONTEXT_SYSTEM:
3359             $name = get_string('coresystem');
3360             break;
3362         case CONTEXT_USER:
3363             if ($user = $DB->get_record('user', array('id'=>$context->instanceid))) {
3364                 if ($withprefix){
3365                     $name = get_string('user').': ';
3366                 }
3367                 $name .= fullname($user);
3368             }
3369             break;
3371         case CONTEXT_COURSECAT:
3372             if ($category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) {
3373                 if ($withprefix){
3374                     $name = get_string('category').': ';
3375                 }
3376                 $name .=format_string($category->name);
3377             }
3378             break;
3380         case CONTEXT_COURSE:
3381             if ($context->instanceid == SITEID) {
3382                 $name = get_string('frontpage', 'admin');
3383             } else {
3384                 if ($course = $DB->get_record('course', array('id'=>$context->instanceid))) {
3385                     if ($withprefix){
3386                         $name = get_string('course').': ';
3387                     }
3388                     if ($short){
3389                         $name .= format_string($course->shortname);
3390                     } else {
3391                         $name .= format_string($course->fullname);
3392                    }
3393                 }
3394             }
3395             break;
3397         case CONTEXT_MODULE:
3398             if ($cm = $DB->get_record_sql('SELECT cm.*, md.name AS modname FROM {course_modules} cm ' .
3399                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3400                 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
3401                         if ($withprefix){
3402                         $name = get_string('modulename', $cm->modname).': ';
3403                         }
3404                         $name .= $mod->name;
3405                     }
3406                 }
3407             break;
3409         case CONTEXT_BLOCK:
3410             if ($blockinstance = $DB->get_record('block_instances', array('id'=>$context->instanceid))) {
3411                 global $CFG;
3412                 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
3413                 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
3414                 $blockname = "block_$blockinstance->blockname";
3415                 if ($blockobject = new $blockname()) {
3416                     if ($withprefix){
3417                         $name = get_string('block').': ';
3418                     }
3419                     $name .= $blockobject->title;
3420                 }
3421             }
3422             break;
3424         default:
3425             print_error('unknowncontext');
3426             return false;
3427     }
3429     return $name;
3432 /**
3433  * Get a URL for a context, if there is a natural one. For example, for
3434  * CONTEXT_COURSE, this is the course page. For CONTEXT_USER it is the
3435  * user profile page.
3436  *
3437  * @param object $context the context.
3438  * @return moodle_url
3439  */
3440 function get_context_url($context) {
3441     global $COURSE, $DB;
3443     switch ($context->contextlevel) {
3444         case CONTEXT_USER:
3445             $url = new moodle_url('/user/view.php', array('id'=>$context->instanceid));
3446             if ($COURSE->id != SITEID) {
3447                 $url->param('courseid', $COURSE->id);
3448             }
3449             return $url;;
3451         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
3452             return new moodle_url('/course/category.php', array('id'=>$context->instanceid));
3454         case CONTEXT_COURSE: // 1 to 1 to course cat
3455             if ($context->instanceid != SITEID) {
3456                 return new moodle_url('/course/view.php', array('id'=>$context->instanceid));
3457             }
3458             break;
3460         case CONTEXT_MODULE: // 1 to 1 to course
3461             if ($modname = $DB->get_field_sql('SELECT md.name AS modname FROM {course_modules} cm ' .
3462                     'JOIN {modules} md ON md.id = cm.module WHERE cm.id = ?', array($context->instanceid))) {
3463                 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$context->instanceid));
3464             }
3465             break;
3467         case CONTEXT_BLOCK:
3468             $parentcontexts = get_parent_contexts($context, false);
3469             $parent = reset($parentcontexts);
3470             $parent = get_context_instance_by_id($parent);
3471             return get_context_url($parent);
3472     }
3474     return new moodle_url('/');
3477 /**
3478  * Returns an array of all the known types of risk
3479  * The array keys can be used, for example as CSS class names, or in calls to
3480  * print_risk_icon. The values are the corresponding RISK_ constants.
3481  *
3482  * @return array all the known types of risk.
3483  */
3484 function get_all_risks() {
3485     return array(
3486         'riskmanagetrust' => RISK_MANAGETRUST,
3487         'riskconfig' => RISK_CONFIG,
3488         'riskxss' => RISK_XSS,
3489         'riskpersonal' => RISK_PERSONAL,
3490         'riskspam' => RISK_SPAM,
3491         'riskdataloss' => RISK_DATALOSS,
3492     );
3495 /**
3496  * Return a link to moodle docs for a given capability name
3497  *
3498  * @global object
3499  * @param object $capability a capability - a row from the mdl_capabilities table.
3500  * @return string the human-readable capability name as a link to Moodle Docs.
3501  */
3502 function get_capability_docs_link($capability) {
3503     global $CFG;
3504     $url = get_docs_url('Capabilities/' . $capability->name);
3505     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
3508 /**
3509  * Extracts the relevant capabilities given a contextid.
3510  * All case based, example an instance of forum context.
3511  * Will fetch all forum related capabilities, while course contexts
3512  * Will fetch all capabilities
3513  *
3514  * capabilities
3515  * `name` varchar(150) NOT NULL,
3516  * `captype` varchar(50) NOT NULL,
3517  * `contextlevel` int(10) NOT NULL,
3518  * `component` varchar(100) NOT NULL,
3519  *
3520  * @global object
3521  * @global object
3522  * @param object context
3523  * @return array
3524  */
3525 function fetch_context_capabilities($context) {
3526     global $DB, $CFG;
3528     $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
3530     $params = array();
3532     switch ($context->contextlevel) {
3534         case CONTEXT_SYSTEM: // all
3535             $SQL = "SELECT *
3536                       FROM {capabilities}";
3537         break;
3539         case CONTEXT_USER:
3540             $extracaps = array('moodle/grade:viewall');
3541             list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3542             $SQL = "SELECT *
3543                       FROM {capabilities}
3544                      WHERE contextlevel = ".CONTEXT_USER."
3545                            OR name $extra";
3546         break;
3548         case CONTEXT_COURSECAT: // course category context and bellow
3549             $SQL = "SELECT *
3550                       FROM {capabilities}
3551                      WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3552         break;
3554         case CONTEXT_COURSE: // course context and bellow
3555             $SQL = "SELECT *
3556                       FROM {capabilities}
3557                      WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3558         break;
3560         case CONTEXT_MODULE: // mod caps
3561             $cm = $DB->get_record('course_modules', array('id'=>$context->instanceid));
3562             $module = $DB->get_record('modules', array('id'=>$cm->module));
3564             $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
3565             if (file_exists($modfile)) {
3566                 include_once($modfile);
3567                 $modfunction = $module->name.'_get_extra_capabilities';
3568                 if (function_exists($modfunction)) {
3569                     $extracaps = $modfunction();
3570                 }
3571             }
3572             if(empty($extracaps)) {
3573                 $extracaps = array();
3574             }
3576             // All modules allow viewhiddenactivities. This is so you can hide
3577             // the module then override to allow specific roles to see it.
3578             // The actual check is in course page so not module-specific
3579             $extracaps[]="moodle/course:viewhiddenactivities";
3580             list($extra, $params) = $DB->get_in_or_equal(
3581                 $extracaps, SQL_PARAMS_NAMED, 'cap0');
3582             $extra = "OR name $extra";
3584             $SQL = "SELECT *
3585                       FROM {capabilities}
3586                      WHERE (contextlevel = ".CONTEXT_MODULE."
3587                            AND component = :component)
3588                            $extra";
3589             $params['component'] = "mod_$module->name";
3590         break;
3592         case CONTEXT_BLOCK: // block caps
3593             $bi = $DB->get_record('block_instances', array('id' => $context->instanceid));
3595             $extra = '';
3596             $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
3597             if ($extracaps) {
3598                 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap0');
3599                 $extra = "OR name $extra";
3600             }
3602             $SQL = "SELECT *
3603                       FROM {capabilities}
3604                      WHERE (contextlevel = ".CONTEXT_BLOCK."
3605                            AND component = :component)
3606                            $extra";
3607             $params['component'] = 'block_' . $bi->blockname;
3608         break;
3610         default:
3611         return false;
3612     }
3614     if (!$records = $DB->get_records_sql($SQL.' '.$sort, $params)) {
3615         $records = array();
3616     }
3618     return $records;
3622 /**
3623  * This function pulls out all the resolved capabilities (overrides and
3624  * defaults) of a role used in capability overrides in contexts at a given
3625  * context.
3626  *
3627  * @global object
3628  * @param obj $context
3629  * @param int $roleid
3630  * @param string $cap capability, optional, defaults to ''
3631  * @param bool if set to true, resolve till this level, else stop at immediate parent level
3632  * @return array
3633  */
3634 function role_context_capabilities($roleid, $context, $cap='') {
3635     global $DB;
3637     $contexts = get_parent_contexts($context);
3638     $contexts[] = $context->id;
3639     $contexts = '('.implode(',', $contexts).')';
3641     $params = array($roleid);
3643     if ($cap) {
3644         $search = " AND rc.capability = ? ";
3645         $params[] = $cap;
3646     } else {
3647         $search = '';
3648     }
3650     $sql = "SELECT rc.*
3651               FROM {role_capabilities} rc, {context} c
3652              WHERE rc.contextid in $contexts
3653                    AND rc.roleid = ?
3654                    AND rc.contextid = c.id $search
3655           ORDER BY c.contextlevel DESC, rc.capability DESC";
3657     $capabilities = array();
3659     if ($records = $DB->get_records_sql($sql, $params)) {
3660         // We are traversing via reverse order.
3661         foreach ($records as $record) {
3662             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
3663             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
3664                 $capabilities[$record->capability] = $record->permission;
3665             }
3666         }
3667     }
3668     return $capabilities;
3671 /**
3672  * Recursive function which, given a context, find all parent context ids,
3673  * and return the array in reverse order, i.e. parent first, then grand
3674  * parent, etc.
3675  *
3676  * @param object $context
3677  * @param bool $capability optional, defaults to false
3678  * @return array
3679  */
3680 function get_parent_contexts($context, $includeself = false) {
3682     if ($context->path == '') {
3683         return array();
3684     }
3686     $parentcontexts = substr($context->path, 1); // kill leading slash
3687     $parentcontexts = explode('/', $parentcontexts);
3688     if (!$includeself) {
3689         array_pop($parentcontexts); // and remove its own id
3690     }
3692     return array_reverse($parentcontexts);
3695 /**
3696  * Return the id of the parent of this context, or false if there is no parent (only happens if this
3697  * is the site context.)
3698  *
3699  * @param object $context
3700  * @return integer the id of the parent context.
3701  */
3702 function get_parent_contextid($context) {
3703     $parentcontexts = get_parent_contexts($context);
3704     if (count($parentcontexts) == 0) {
3705         return false;
3706     }
3707     return array_shift($parentcontexts);
3710 /**
3711  * Check if contect is the front page context or a context inside it
3712  *
3713  * Returns true if this context is the front page context, or a context inside it,
3714  * otherwise false.
3715  *
3716  * @param object $context a context object.
3717  * @return bool
3718  */
3719 function is_inside_frontpage($context) {
3720     $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
3721     return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
3724 /**
3725  * Runs get_records select on context table and returns the result
3726  * Does get_records_select on the context table, and returns the results ordered
3727  * by contextlevel, and then the natural sort order within each level.
3728  * for the purpose of $select, you need to know that the context table has been
3729  * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3');
3730  *
3731  * @global object
3732  * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname.
3733  * @param array $params any parameters required by $select.
3734  * @return array the requested context records.
3735  */
3736 function get_sorted_contexts($select, $params = array()) {
3737     global $DB;
3738     if ($select) {
3739         $select = 'WHERE ' . $select;
3740     }
3741     return $DB->get_records_sql("
3742             SELECT ctx.*
3743             FROM {context} ctx
3744             LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid
3745             LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid
3746             LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid
3747             LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid
3748             LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid
3749             $select
3750             ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id
3751             ", $params);
3754 /**
3755  * Recursive function which, given a context, find all its children context ids.
3756  *
3757  * When called for a course context, it will return the modules and blocks
3758  * displayed in the course page.
3759  *
3760  * For course category contexts it will return categories and courses. It will
3761  * NOT recurse into courses, nor return blocks on the category pages. If you
3762  * want to do that, call it on the returned courses.
3763  *
3764  * If called on a course context it _will_ populate the cache with the appropriate
3765  * contexts ;-)
3766  *
3767  * @param object $context.
3768  * @return array Array of child records
3769  */
3770 function get_child_contexts($context) {
3772     global $DB, $ACCESSLIB_PRIVATE;
3774     // We *MUST* populate the context_cache as the callers
3775     // will probably ask for the full record anyway soon after
3776     // soon after calling us ;-)
3778     $array = array();
3780     switch ($context->contextlevel) {
3782         case CONTEXT_BLOCK:
3783             // No children.
3784         break;
3786         case CONTEXT_MODULE:
3787             // Find
3788             // - blocks under this context path.
3789             $sql = " SELECT ctx.*
3790                        FROM {context} ctx
3791                       WHERE ctx.path LIKE ?
3792                             AND ctx.contextlevel = ".CONTEXT_BLOCK;
3793             $params = array("{$context->path}/%", $context->instanceid);
3794             $records = $DB->get_recordset_sql($sql, $params);
3795             foreach ($records as $rec) {
3796                 cache_context($rec);
3797                 $array[$rec->id] = $rec;
3798             }
3799             break;
3801         case CONTEXT_COURSE:
3802             // Find
3803             // - modules and blocks under this context path.
3804             $sql = " SELECT ctx.*
3805                        FROM {context} ctx
3806                       WHERE ctx.path LIKE ?
3807                             AND ctx.contextlevel IN (".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
3808             $params = array("{$context->path}/%", $context->instanceid);
3809             $records = $DB->get_recordset_sql($sql, $params);
3810             foreach ($records as $rec) {
3811                 cache_context($rec);
3812                 $array[$rec->id] = $rec;
3813             }
3814         break;
3816         case CONTEXT_COURSECAT:
3817             // Find
3818             // - categories
3819             // - courses
3820             $sql = " SELECT ctx.*
3821                        FROM {context} ctx
3822                       WHERE ctx.path LIKE ?
3823                             AND ctx.contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.")";
3824             $params = array("{$context->path}/%");
3825             $records = $DB->get_recordset_sql($sql, $params);
3826             foreach ($records as $rec) {
3827                 cache_context($rec);
3828                 $array[$rec->id] = $rec;
3829             }
3830         break;
3832         case CONTEXT_USER:
3833             // Find
3834             // - blocks under this context path.
3835             $sql = " SELECT ctx.*
3836                        FROM {context} ctx
3837                       WHERE ctx.path LIKE ?
3838                             AND ctx.contextlevel = ".CONTEXT_BLOCK;
3839             $params = array("{$context->path}/%", $context->instanceid);
3840             $records = $DB->get_recordset_sql($sql, $params);
3841             foreach ($records as $rec) {
3842                 cache_context($rec);
3843                 $array[$rec->id] = $rec;
3844             }
3845             break;
3847         case CONTEXT_SYSTEM:
3848             // Just get all the contexts except for CONTEXT_SYSTEM level
3849             // and hope we don't OOM in the process - don't cache
3850             $sql = "SELECT c.*
3851                       FROM {context} c
3852                      WHERE contextlevel != ".CONTEXT_SYSTEM;
3854             $records = $DB->get_records_sql($sql);
3855             foreach ($records as $rec) {
3856                 $array[$rec->id] = $rec;
3857             }
3858         break;
3860         default:
3861             print_error('unknowcontext', '', '', $context->contextlevel);
3862             return false;
3863     }
3864     return $array;
3868 /**
3869  * Gets a string for sql calls, searching for stuff in this context or above
3870  *
3871  * @param object $context
3872  * @return string
3873  */
3874 function get_related_contexts_string($context) {
3875     if ($parents = get_parent_contexts($context)) {
3876         return (' IN ('.$context->id.','.implode(',', $parents).')');
3877     } else {
3878         return (' ='.$context->id);
3879     }
3882 /**
3883  * Verifies if given capability installed.
3884  *
3885  * @global object
3886  * @param string $capabilityname
3887  * @param bool $cached
3888  * @return book true if capability exists
3889  */
3890 function is_valid_capability($capabilityname, $cached = true) {
3891     global $ACCESSLIB_PRIVATE; // one request per page only
3893     if (is_null($ACCESSLIB_PRIVATE->capabilitynames) or !$cached) {
3894         global $DB;
3895         $ACCESSLIB_PRIVATE->capabilitynames = $DB->get_records_menu('capabilities', null, '', 'name, 1');
3896     }
3898     return array_key_exists($capabilityname, $ACCESSLIB_PRIVATE->capabilitynames);
3901 /**
3902  * Returns the human-readable, translated version of the capability.
3903  * Basically a big switch statement.
3904  *
3905  * @param string $capabilityname e.g. mod/choice:readresponses
3906  * @return string
3907  */
3908 function get_capability_string($capabilityname) {
3910     // Typical capabilityname is mod/choice:readresponses
3912     $names = split('/', $capabilityname);
3913     $stringname = $names[1];                 // choice:readresponses
3914     $components = split(':', $stringname);
3915     $componentname = $components[0];               // choice
3917     switch ($names[0]) {
3918         case 'report':
3919             $string = get_string($stringname, 'report_'.$componentname);
3920         break;
3922         case 'mod':
3923             $string = get_string($stringname, $componentname);
3924         break;
3926         case 'block':
3927             $string = get_string($stringname, 'block_'.$componentname);
3928         break;
3930         case 'moodle':
3931             if ($componentname == 'local') {
3932                 $string = get_string($stringname, 'local');
3933             } else {
3934                 $string = get_string($stringname, 'role');
3935             }
3936         break;
3938         case 'enrol':
3939             $string = get_string($stringname, 'enrol_'.$componentname);
3940         break;
3942         case 'format':
3943             $string = get_string($stringname, 'format_'.$componentname);
3944         break;
3946         case 'format':
3947             $string = get_string($stringname, 'editor_'.$componentname);
3948         break;
3950         case 'gradeexport':
3951             $string = get_string($stringname, 'gradeexport_'.$componentname);
3952         break;
3954         case 'gradeimport':
3955             $string = get_string($stringname, 'gradeimport_'.$componentname);
3956         break;
3958         case 'gradereport':
3959             $string = get_string($stringname, 'gradereport_'.$componentname);
3960         break;
3962         case 'coursereport':
3963             $string = get_string($stringname, 'coursereport_'.$componentname);
3964         break;
3966         case 'quizreport':
3967             $string = get_string($stringname, 'quiz_'.$componentname);
3968         break;
3970         case 'repository':
3971             $string = get_string($stringname, 'repository_'.$componentname);
3972         break;
3974         case 'local':
3975             $string = get_string($stringname, 'local_'.$componentname);
3976         break;
3978         case 'webservice':
3979             $string = get_string($stringname, 'webservice_'.$componentname);
3980         break;
3982         default:
3983             $string = get_string($stringname);
3984         break;
3986     }
3987     return $string;
3991 /**
3992  * This gets the mod/block/course/core etc strings.
3993  *
3994  * @param string $component
3995  * @param int $contextlevel
3996  * @return string|bool String is success, false if failed
3997  */
3998 function get_component_string($component, $contextlevel) {
4000     switch ($contextlevel) {
4002         case CONTEXT_SYSTEM:
4003             if (preg_match('|^enrol/|', $component)) {
4004                 $langname = str_replace('/', '_', $component);
4005                 $string = get_string('enrolname', $langname);
4006             } else if (preg_match('|^block/|', $component)) {
4007                 $langname = str_replace('/', '_', $component);
4008                 $string = get_string('blockname', $langname);
4009             } else if (preg_match('|^local|', $component)) {
4010                 $langname = str_replace('/', '_', $component);
4011                 $string = get_string('local');
4012             } else if (preg_match('|^report/|', $component)) {
4013                 $string = get_string('reports');
4014             } else {
4015                 $string = get_string('coresystem');
4016             }
4017         break;
4019         case CONTEXT_USER:
4020             $string = get_string('users');
4021         break;
4023         case CONTEXT_COURSECAT:
4024             $string = get_string('categories');
4025         break;
4027         case CONTEXT_COURSE:
4028             if (preg_match('|^gradeimport/|', $component)
4029                 || preg_match('|^gradeexport/|', $component)
4030                 || preg_match('|^gradereport/|', $component)) {
4031                 $string = get_string('gradebook', 'admin');
4032             } else if (preg_match('|^coursereport/|', $component)) {
4033                 $string = get_string('coursereports');
4034             } else if (preg_match('|^webservice/|', $component)) {
4035                 $string = get_string('webservices', 'webservice');
4036             } else {
4037                 $string = get_string('course');
4038             }
4039         break;
4041         case CONTEXT_MODULE:
4042             if (preg_match('|^quiz_([a-z_]*)|', $component, $matches)){
4043                 $langname = 'quiz_'.$matches[1];
4044                 $string = get_string($matches[1].':componentname', $langname);
4045             } else {
4046                 $string = get_string('modulename', preg_replace('#(\w+_)#', '', basename($component)));
4047             }
4048         break;
4050         case CONTEXT_BLOCK:
4051             if( $component == 'moodle' ){
4052                 $string = get_string('block');
4053             }else{
4054                 $string = get_string('blockname', basename($component));
4055             }
4056         break;
4058         default:
4059             print_error('unknowncontext');
4060         return false;
4062     }
4063     return $string;
4066 /**
4067  * Gets the list of roles assigned to this context and up (parents)
4068  *
4069  * set $view to true when roles are pulled for display only
4070  * this is so that we can filter roles with no visible
4071  * assignment, for example, you might want to "hide" all
4072  * course creators when browsing the course participants
4073  * list.
4074  *
4075  * @global object
4076  * @param object $context
4077  * @param bool $view
4078  * @return array
4079  */
4080 function get_roles_used_in_context($context, $view = false) {
4081     global $DB;
4083     // filter for roles with all hidden assignments
4084     // no need to return when only pulling roles for reviewing
4085     // e.g. participants page.
4086     $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context))? ' AND ra.hidden = 0 ':'';
4087     $contextlist = get_related_contexts_string($context);
4089     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder
4090               FROM {role_assignments} ra, {role} r
4091              WHERE r.id = ra.roleid
4092                    AND ra.contextid $contextlist
4093                    $hiddensql
4094           ORDER BY r.sortorder ASC";
4096     return $DB->get_records_sql($sql);
4099 /**
4100  * This function is used to print roles column in user profile page.
4101  *
4102  * @global object
4103  * @global object
4104  * @global object
4105  * @param int $userid
4106  * @param object $context
4107  * @param bool $view
4108  * @return string
4109  */
4110 function get_user_roles_in_context($userid, $context, $view=true){
4111     global $CFG, $DB,$USER;
4113     $rolestring = '';
4114     $sql = "SELECT *
4115             FROM {role_assignments} ra, {role} r
4116            WHERE ra.userid = ? and ra.contextid = ? and ra.roleid = r.id";
4117     $params = array($userid, $context->id);
4118     $rolenames = array();
4119     if ($roles = $DB->get_records_sql($sql, $params)) {
4120         foreach ($roles as $userrole) {
4121             // MDL-12544, if we are in view mode and current user has no capability to view hidden assignment, skip it
4122             if ($userrole->hidden && $view && !has_capability('moodle/role:viewhiddenassigns', $context)) {
4123                 continue;
4124             }
4125             $rolenames[$userrole->roleid] = $userrole->name;
4126         }
4128         $rolenames = role_fix_names($rolenames, $context);   // Substitute aliases
4130         foreach ($rolenames as $roleid => $rolename) {
4131             $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
4132         }
4133         $rolestring = implode(',', $rolenames);
4134     }
4135     return $rolestring;
4139 /**
4140  * Checks if a user can override capabilities of a particular role in this context
4141  *
4142  * @deprecated As of version 2.0
4143  * @todo not needed anymore, remove in 2.0
4144  * @param object $context
4145  * @param int $targetroleid the id of the role you want to override
4146  * @return boolean
4147  */
4148 function user_can_override($context, $targetroleid) {
4150 // TODO: not needed anymore, remove in 2.0
4152     global $DB;
4153     // first check if user has override capability
4154     // if not return false;
4155     if (!has_capability('moodle/role:override', $context)) {
4156         return false;
4157     }
4158     // pull out all active roles of this user from this context(or above)
4159     if ($userroles = get_user_roles($context)) {
4160         foreach ($userroles as $userrole) {
4161             // if any in the role_allow_override table, then it's ok
4162             if ($DB->get_record('role_allow_override', array('roleid'=>$userrole->roleid, 'allowoverride'=>$targetroleid))) {
4163                 return true;
4164             }
4165         }
4166     }
4168     return false;
4172 /**
4173  * Checks if a user can assign users to a particular role in this context
4174  *
4175  * @global object
4176  * @param object $context
4177  * @param int $targetroleid - the id of the role you want to assign users to
4178  * @return boolean
4179  */
4180 function user_can_assign($context, $targetroleid) {
4181     global $DB;
4183     // first check if user has override capability
4184     // if not return false;
4185     if (!has_capability('moodle/role:assign', $context)) {
4186         return false;
4187     }
4188     // pull out all active roles of this user from this context(or above)
4189     if ($userroles = get_user_roles($context)) {
4190         foreach ($userroles as $userrole) {
4191             // if any in the role_allow_override table, then it's ok
4192             if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
4193                 return true;
4194             }
4195         }
4196     }
4198     return false;
4201 /**
4202  * Returns all site roles in correct sort order.
4203  *
4204  * @global object
4205  * @return array
4206  */
4207 function get_all_roles() {
4208     global $DB;
4209     return $DB->get_records('role', null, 'sortorder ASC');
4212 /**
4213  * gets all the user roles assigned in this context, or higher contexts
4214  * this is mainly used when checking if a user can assign a role, or overriding a role
4215  * i.e. we need to know what this user holds, in order to verify against allow_assign and
4216  * allow_override tables
4217  *
4218  * set $view to true when roles are pulled for display only
4219  * this is so that we can filter roles with no visible
4220  * assignment, for example, you might want to "hide" all
4221  * course creators when browsing the course participants
4222  * list.
4223  *
4224  * @global object
4225  * @global object
4226  * @param object $context
4227  * @param int $userid
4228  * @param bool $checkparentcontexts defaults to true
4229  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
4230  * @param bool $view
4231  * @return array
4232  */
4233 function get_user_roles($context, $userid=0, $checkparentcontexts=true, $order='c.contextlevel DESC, r.sortorder ASC', $view=false) {
4234     global $USER, $DB;
4236     if (empty($userid)) {
4237         if (empty($USER->id)) {
4238             return array();
4239         }
4240         $userid = $USER->id;
4241     }
4242     // set up hidden sql
4243     $hiddensql = ($view && !has_capability('moodle/role:viewhiddenassigns', $context)) ? "AND ra.hidden = 0" : "";
4245     if ($checkparentcontexts) {
4246         $contextids = get_parent_contexts($context);
4247     } else {
4248         $contextids = array();
4249     }
4250     $contextids[] = $context->id;
4252     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
4254     array_unshift($params, $userid);
4256     $sql = "SELECT ra.*, r.name, r.shortname
4257               FROM {role_assignments} ra, {role} r, {context} c
4258              WHERE ra.userid = ?
4259                    AND ra.roleid = r.id
4260                    AND ra.contextid = c.id
4261                    AND ra.contextid $contextids
4262                    $hiddensql
4263           ORDER BY $order";
4265     return $DB->get_records_sql($sql ,$params);
4268 /**
4269  * Creates a record in the role_allow_override table
4270  *
4271  * @global object
4272  * @param int $sroleid source roleid
4273  * @param int $troleid target roleid
4274  * @return int id or false
4275  */
4276 function allow_override($sroleid, $troleid) {
4277     global $DB;
4279     $record = new object();
4280     $record->roleid        = $sroleid;
4281     $record->allowoverride = $troleid;
4282     $DB->insert_record('role_allow_override', $record);
4285 /**
4286  * Creates a record in the role_allow_assign table
4287  *
4288  * @global object
4289  * @param int $sroleid source roleid
4290  * @param int $troleid target roleid
4291  * @return int id or false